diff --git a/.claude/skills/docs-skill/SKILL.md b/.claude/skills/docs-skill/SKILL.md new file mode 100644 index 0000000000..ad44bea9c5 --- /dev/null +++ b/.claude/skills/docs-skill/SKILL.md @@ -0,0 +1,36 @@ +--- +name: docs-skill +description: Conventions and verification workflow for writing and restructuring the documentation site (docs/content). Should be used when adding, editing, or reviewing docs pages. +--- + +# Documentation + +The documentation site lives in `docs/content/docs` (fumadocs + Next.js). These are the conventions for writing and verifying it. + +## Structure principles + +- **Simple first, architecture later.** Lead every feature section with the shortest working setup (install, one snippet, "that's all"). Explain the underlying architecture and extension points afterwards, or in a separate "Custom …" section. Never make the reader wade through the extension contract to find the pre-configured default. +- **One page per feature the user searches for.** Users scan the sidebar for the thing they want — don't bury a feature inside a page about a different feature. Splitting a long page is preferable to a grab-bag: stale content hides in 400-line pages and gets found in 100-line ones. +- **Single-source each mechanism.** Document a mechanism in exactly one place — the page that owns the concept — and link to it from everywhere else it's relevant. Copies of the same explanation on multiple pages drift apart independently. +- **Complete implementations are live examples; page snippets are walkthroughs.** A page-length code block duplicating a full implementation is never compiled or run, so it silently rots — put the complete implementation in an embedded example (which is type-checked and rendered, so it can't) and keep page snippets small, each carrying one concept: a config shape, a component invocation, a signature. +- **Setup snippets are self-contained.** A snippet the reader is meant to copy includes the imports for each symbol it uses — without them it doesn't work when copied. Walkthrough fragments of an implementation the page's example completes may omit imports. +- **Order sections by audience breadth.** Content most readers need comes first; content specific to one feature or use case goes at the end of the page, even when it feels related to an earlier section. A reader following a cross-link to the specific section finds it regardless of position — the reader skimming the page shouldn't wade through it. +- **Live examples cap the setup they demonstrate.** Place an `` embed after the prose has introduced everything the example's code uses — an embed whose `App.tsx` shows APIs the page never mentions teaches by confusion. Introduced means named, given a purpose, and linked — the example itself (and the linked component pages) can carry the full wiring; expanding every integration inline pushes the example too far down the page. Not at the top (a demo without context motivates but doesn't teach) and not at the bottom (readers rarely reach it); at the end of the "getting it working" narrative, before advanced/optional topics. If an example uses more API than the page should cover, simplify the example rather than the rule. + +- **Signature snippets tell the truth for one API.** A snippet formatted as a type signature documents exactly that export — never fold variant differences ("only for the X subpath…") into a doc-comment inside it. Show the signature that is true everywhere, and describe variant-specific options as prose in the variant's own section. +- **Caveats must be actionable.** Only note a limitation if the reader can do something with it (install a package, avoid a pattern, pass an option). Speculative hedges ("rare X may behave differently") and defensive implementation details (what a function guards against internally) erode trust without helping anyone act — cut them. + +## Verifying content + +- **Verify snippets against the actual package exports, not memory or existing docs.** APIs drift; grep the package source for every symbol a snippet imports (`export function X` / `export const X`) and check option names and shapes. Content copied forward without this check stays wrong after refactors. +- `node docs/validate-links.mjs` validates internal routes **and** `#anchors` (works from any directory). Anchors follow github-slugger: lowercased, spaces to dashes, symbols dropped — "A & B" becomes `#a--b`. +- **Render before declaring done**: `pnpm --filter docs run dev` (port 3000), then check each touched route returns 200 (the dev server compiles MDX on demand, so a 200 also proves the MDX compiles) and grep the HTML for expected headings/anchor ids. For visual checks, `cd tests && pnpm exec playwright screenshot --full-page --wait-for-timeout 8000 ` and inspect the image — example embeds load lazily, so give them the wait. + +## Site mechanics + +- Navigation order comes from each directory's `meta.json` (`pages` array, `"..."` = the rest alphabetically). Directories without a `meta.json` sort alphabetically. +- `index.mdx` pages ending in `` list their section's pages automatically from frontmatter — new pages appear without extra wiring. +- `` embeds a live example; the name is the example's slug without number prefixes (`examples/06-custom-schema/09-math-block` → `custom-schema/math-block`). +- Frontmatter needs `title` and `description`; the description doubles as the CardTable card text. +- Strange dev-server behavior — `JSON.parse` errors on unrelated pages, new pages missing from the sidebar — usually means stale or corrupted generated state, not a content bug: restart the dev server (fumadocs' source map is built at startup) and, if errors persist, delete `docs/.next` (regenerable cache) before debugging further. +- Renaming or removing a heading breaks external links to its anchor silently — when restructuring, leave a pointer (e.g. a short "Related" section) where a well-known anchor used to be. diff --git a/.claude/skills/testing-skill/SKILL.md b/.claude/skills/testing-skill/SKILL.md index ab70133c11..982d9ea03b 100644 --- a/.claude/skills/testing-skill/SKILL.md +++ b/.claude/skills/testing-skill/SKILL.md @@ -17,9 +17,13 @@ In most cases, once a feature, bug fix, or other modification has been written, `/packages/xl-*`: Contain tests for functionality included in a given `xl-*` package. +### Colocated Browser Unit Tests + +`packages/*/src/**/*.browser.test.{ts,tsx}`: Unit tests for browser-only implementations (e.g. canvas or DOM-dependent code) live next to the code they test, with a `.browser.test` suffix. They run as part of the browser suite in Docker (the `tests` package's browser config includes them); the packages' own node-mode vitest configs exclude them. Use this when the unit under test genuinely needs a real browser — everything else should be a plain node unit test. + ### End-to-End Tests -`tests/src/end-to-end`: Any test which interacts with the editor UI or simulates user interaction goes here. New subdirectories can be added if the functionality being tested is not covered by any of the existing ones. Important note about existing E2E tests - many are written poorly and should only loosely be used as reference. We want to avoid abstraction layers and `waitForTimeout` as much as possible. +`tests/src/end-to-end`: Tests that need a real browser and span multiple packages go here — chiefly tests which interact with the editor UI or simulate user interaction, but also browser integration tests that exercise complete flows without interaction (e.g. exporting a full document, static rendering). New subdirectories can be added if the functionality being tested is not covered by any of the existing ones. Important note about existing E2E tests - many are written poorly and should only loosely be used as reference. We want to avoid abstraction layers and `waitForTimeout` as much as possible. ## When & How to Add Tests @@ -29,6 +33,8 @@ However, this may not be true when adding edge case handling or a new feature, w We want to avoid adding end-to-end tests where it's possible to use unit tests instead. +**Don't use jsdom** (`@vitest-environment jsdom`) in new tests. It's a murky middle ground — `document` exists but rendering doesn't — which makes browser-capability checks pass while the capability itself is broken. Use the default node environment with pluggable seams for logic, and the browser suite (`tests/src/end-to-end`, vitest browser mode in Docker) for anything that needs real rendering. + ## Running & Updating Tests ### Unit Tests @@ -39,24 +45,18 @@ Updating tests can be done by adding the `-u` argument, i.e. `vp run test -u`. A ### End-to-End Tests -End-to-end tests run inside a docker container. While its possible to run them outside of it, we do not have existing snapshots to compare results with, and the results sometimes differ to when they're run within Docker, so it's not worth doing. - -To run end-to-end tests, you must first build the project and run the preview. You can do this by running `vp start` from the root directory. +End-to-end tests run in vitest browser mode (chromium, firefox and webkit) inside a Docker container, so screenshot baselines are identical locally and on CI. Run them from the repository root: -You can then run the tests from the `/tests` directory using the following command: - -``` -docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.51.1-noble npx playwright test +```bash +bash tests/docker-run.sh -e CI=1 -- --run [filters] ``` -A specific test file may be targeted by appending its name, i.e. `... npx playwright test fileName`. Individual tests in a file may be disabled using `skip`, i.e. `test.skip("Test name", ...)` (remember to revert this once all tests pass). +A specific test file may be targeted by appending (part of) its name as a filter. A single browser may be targeted with `--project "e2e (chromium)"`. Individual tests in a file may be disabled using `skip`, i.e. `test.skip("Test name", ...)` (remember to revert this once all tests pass). -Updating tests can be done by adding the `-u` argument, i.e. `... npx playwright test -u`. All of the other things you can do to scope which tests to target still apply. +Screenshot baselines can be regenerated with the `-u` argument, which must come **after** the filters (`--run -u`): written as `--run -u `, the filter is parsed as the flag's value and the **whole** suite runs in update mode, silently rewriting unrelated baselines. Note that `-u` only rewrites baselines whose comparison **fails** — a small intended change (e.g. a short text edit) that fits inside the suite's 2% pixel tolerance leaves the baseline stale while the test passes. To force a fresh capture, delete the baseline file first. Baselines are per-browser (`--linux.png`); after regenerating, always inspect the images before committing them. -Note that running this command may result in errors or other issues, listed below along with what to do when encountered: - -- **Tests failing to navigating to preview**: project should be built and the preview started, after which the command should be run again. -- **Docker not running**: the user should be notified to launch Docker. -- **Incorrect Playwright image version**: update Playwright images and re-run the command. +If Docker isn't running, notify the user to launch it. When testing a visual change, prefer writing screenshots to verify that the change is working as expected. + +**Screenshots of tall content**: browser-suite tests run inside a tester iframe sized to the browser window (1280x720), and element screenshots only contain what the iframe actually paints — anything below its fold captures as blank white, silently. Growing the iframe with `page.viewport()` alone doesn't fix this at full resolution: the harness scales the iframe down to fit the window, shrinking the resulting baseline (`static.test.tsx` accepts that trade-off). For full-resolution captures use `screenshotFull` (`tests/src/utils/screenshotFull.ts`), which grows the iframe past the content and neutralizes the harness's scale transform during the capture — the same mechanism upstream Vitest adopted in vitest-dev/vitest#9745 (milestone 5.0.0; the util can be deleted once vite-plus ships it). Always eyeball newly generated baselines for truncation. diff --git a/AGENTS.md b/AGENTS.md index 8b992c6744..22b10b6527 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,13 @@ BlockNote is a block-based rich text editor for the web. It's designed as a batteries-included product that offers a solid user experience with minimal setup. However, it also offers extensibility via plugins and custom block types. +# Code Conventions + +- **Leverage the type system so mistakes surface at compile time, not runtime.** Model states and outcomes explicitly: discriminated unions over boolean flags with optional fields, no `any` or casts that hide a case a caller should handle, exhaustive `switch`es over union members. If the compiler can enforce a contract, prefer that over documentation or runtime checks. +- **Expected failures are values, not exceptions.** When an operation can fail as part of normal use (canonical example: parsing user input, like LaTeX or Mermaid source), that failure is part of the function's contract — so it belongs in the return type. Catch it at the lowest level (the small adapter around the throwing third-party call) and convert it into a Result-style discriminated union (e.g. `{ error?: undefined; ...data } | { error: string }`). The failure then propagates through the type system, and the compiler forces every caller to decide how to handle it. Exceptions don't appear in TypeScript signatures, so a thrown expected error is invisible to callers — and a `try/catch` around a whole pipeline conflates expected failures with genuine bugs. +- **Exceptions are only for unexpected failures** — broken invariants, environment or infrastructure problems, programmer errors. Let them propagate and fail loudly; don't catch-and-continue. Corollary: never render a caught exception's message into user-facing output (documents, UI) — a catch-all can capture anything, and arbitrary messages can leak internals. Only messages carried by typed expected-error results are known-safe to display. +- **Prefer `function name() {}` declarations over `const name = () => {}`** for named functions (anonymous callbacks and returned closures can stay arrows). + # Common Commands All commands below are listed under `package.json` in the project root. See `vite.config.ts` for relevant configuration settings. diff --git a/docs/content/docs/features/blocks/code-blocks.mdx b/docs/content/docs/features/blocks/code-blocks.mdx index 8f5d1816b3..32f341e5d8 100644 --- a/docs/content/docs/features/blocks/code-blocks.mdx +++ b/docs/content/docs/features/blocks/code-blocks.mdx @@ -1,13 +1,11 @@ --- title: Code Blocks -description: How to add syntax highlighting to code blocks. +description: How to use code blocks, and how to add syntax highlighting to them. --- # Code Blocks -Code blocks are a simple way to display formatted code with syntax highlighting. - -Code blocks by default are a simple way to display code. But, BlockNote also supports more advanced features like: +Code blocks are a simple way to display formatted code. By default they're kept deliberately simple, but BlockNote also supports more advanced features: - Syntax highlighting - Custom themes @@ -34,7 +32,6 @@ type CodeBlockOptions = { aliases?: string[]; } >; - createHighlighter?: () => Promise>; }; ``` @@ -42,20 +39,7 @@ type CodeBlockOptions = { `defaultLanguage:` The syntax highlighting default language for code blocks which are created/inserted without a set language, which is `text` by default (no syntax highlighting). -`supportedLanguages:` The syntax highlighting languages supported by the code block, which is an empty array by default. - -`createHighlighter:` The [Shiki highliter](https://shiki.style/guide/load-theme) to use for syntax highlighting. - -BlockNote also provides a generic set of options for syntax highlighting in the `@blocknote/code-block` package, which support a wide range of languages: - -```ts -import { createCodeBlockSpec } from "@blocknote/core"; -import { codeBlockOptions } from "@blocknote/code-block"; - -const codeBlock = createCodeBlockSpec(codeBlockOptions); -``` - -See [this example](/examples/theming/code-block) to see it in action. +`supportedLanguages:` The syntax highlighting languages supported by the code block. Empty by default. **Type & Props** @@ -66,57 +50,97 @@ type CodeBlock = { props: { language: string; }; - content: InlineContent[]; + content: StyledText[]; children: Block[]; }; ``` `language:` The syntax highlighting language to use. Defaults to `text`, which has no highlighting. +Unlike most blocks, the code block's `content` is [plain text](/docs/foundations/document-structure#plain-text-content). + +## Syntax Highlighting + +The easiest way to enable syntax highlighting is the `@blocknote/code-block` package. It ships a ready-to-use highlighter supporting a wide range of languages: + +```bash +npm install @blocknote/code-block +``` + +Add its `syntaxHighlighter` extension to your editor, and pass the matching `codeBlockOptions` (the supported language list) to the code block spec: + +```ts +import { createCodeBlockSpec } from "@blocknote/core"; +import { codeBlockOptions, syntaxHighlighter } from "@blocknote/code-block"; + +const editor = useCreateBlockNote({ + extensions: [syntaxHighlighter], + schema: BlockNoteSchema.create().extend({ + blockSpecs: { + codeBlock: createCodeBlockSpec(codeBlockOptions), + }, + }), +}); +``` + +That's all — see it in action in [this example](/examples/theming/code-block). + + + The same extension also highlights other source-authored blocks, like the + [math](/docs/features/blocks/math) and + [diagram](/docs/features/blocks/diagrams) blocks' source popups. + + ## Custom Syntax Highlighting -To create your own syntax highlighter, you can use the [shiki-codegen](https://shiki.style/packages/codegen) CLI for generating the code to create one for your chosen languages and themes. +Under the hood, highlighting is split into two parts, which you can also wire up yourself for full control over the bundle: + +- The `SyntaxHighlightingExtension` (from `@blocknote/core`) provides the highlighter — a [Shiki](https://shiki.style) instance with your choice of languages, themes, and engine: + + ```ts + type SyntaxHighlightingOptions = { + createHighlighter: () => Promise>; + }; + + const syntaxHighlighter = SyntaxHighlightingExtension(options); + ``` + + `createHighlighter:` Creates the [Shiki highlighter](https://shiki.style/guide/load-theme) to use for syntax highlighting. + +- Each block decides if and how its text is highlighted, via the `highlight` callback in the block spec's `meta`: + + ```ts + // In the block spec's `meta`: + highlight?: (block: Block) => string | undefined; + ``` + + It runs for each instance of the block and returns the language to highlight the block's text with (one of the languages supported by the highlighter), or `undefined` for no highlighting. The default code block already implements it, returning its `language` prop — while `codeBlockOptions` supplies the matching set of supported languages for the bundled highlighter. -For example, to create a syntax highlighter using the optimized javascript engine, javascript, typescript, vue, with light and dark themes, you can run the following command: +To create your own highlighter, the [shiki-codegen](https://shiki.style/packages/codegen) CLI generates the code for your chosen languages and themes. For example, for a highlighter using the optimized JavaScript engine with javascript, typescript, and vue, plus light and dark themes: ```bash npx shiki-codegen --langs javascript,typescript,vue --themes light-plus,dark-plus --engine javascript --precompiled ./shiki.bundle.ts ``` -This will generate a `shiki.bundle.ts` file that you can use to create a syntax highlighter for your editor. - -Like this: +This generates a `shiki.bundle.ts` file that you use to create the extension — added to the editor exactly like the pre-configured one above, alongside a `createCodeBlockSpec` configured with your matching languages: ```ts +import { SyntaxHighlightingExtension } from "@blocknote/core"; import { createHighlighter } from "./shiki.bundle.js"; -export default function App() { - const editor = useCreateBlockNote({ - schema: BlockNoteSchema.create().extend({ - blockSpecs: { - codeBlock: createCodeBlockSpec({ - indentLineWithTab: true, - defaultLanguage: "typescript", - supportedLanguages: { - typescript: { - name: "TypeScript", - aliases: ["ts"], - }, - }, - createHighlighter: () => - createHighlighter({ - themes: ["light-plus", "dark-plus"], - langs: [], - }), - }), - }, +const syntaxHighlighter = SyntaxHighlightingExtension({ + createHighlighter: () => + createHighlighter({ + themes: ["light-plus", "dark-plus"], + langs: [], }), - }); - - return ; -} +}); ``` -See the custom code block example for a more detailed example. +The example below shows the complete setup: + +## Related + +Looking for blocks that are *authored* as code but *rendered* as what the code produces? See the [math](/docs/features/blocks/math) and [diagram](/docs/features/blocks/diagrams) blocks, or [build your own](/docs/features/custom-schemas/source-with-preview). diff --git a/docs/content/docs/features/blocks/diagrams.mdx b/docs/content/docs/features/blocks/diagrams.mdx new file mode 100644 index 0000000000..e06f57455e --- /dev/null +++ b/docs/content/docs/features/blocks/diagrams.mdx @@ -0,0 +1,127 @@ +--- +title: Diagrams +description: Mermaid diagram blocks for BlockNote — rendered as diagrams in the editor, and exportable to Markdown, PDF, DOCX, ODT, and email. +--- + +# Diagrams + +The `@blocknote/diagram-block` package adds a **diagram block**: authored as [Mermaid](https://mermaid.js.org/) source in a source popup, rendered as the diagram it describes — flowcharts, sequence diagrams, Gantt charts, and everything else Mermaid supports. + + + This block is only available in React (`@blocknote/react`). + + +```bash +npm install @blocknote/diagram-block +``` + +## Adding to your editor + +The package exports `createReactDiagramBlockSpec`. Add it to your schema's `blockSpecs`: + +```tsx +import { BlockNoteSchema } from "@blocknote/core"; +import { createReactDiagramBlockSpec } from "@blocknote/diagram-block"; + +const schema = BlockNoteSchema.create().extend({ + blockSpecs: { + // Adds the Diagram block to the schema. + diagram: createReactDiagramBlockSpec(), + }, +}); +``` + +To highlight the Mermaid source in the popup, add the [syntax highlighting](/docs/features/blocks/code-blocks#syntax-highlighting) extension to your editor. The diagram block already declares its source language (`mermaid`), so no per-block configuration is needed: + +```tsx +import { syntaxHighlighter } from "@blocknote/code-block"; + +const editor = useCreateBlockNote({ + schema, + extensions: [syntaxHighlighter], +}); +``` + +## Menu items & localization + +Because the diagram spec lives in an optional package, its editor integrations are opt-in too — the package exports everything needed: + +```tsx +import { + getDiagramSlashMenuItems, // Slash Menu item for inserting a diagram + getDiagramBlockTypeSelectItems, // Block Type Select item for the Formatting Toolbar + locales as diagramLocales, // dictionary strings, merged under the `diagram` key +} from "@blocknote/diagram-block"; +``` + +- `getDiagramSlashMenuItems(editor)` returns a [Slash Menu](/docs/react/components/suggestion-menus#slash-menu) item for inserting a diagram — combine it with the default items via `combineByGroup`. +- `getDiagramBlockTypeSelectItems(editor)` returns a [Block Type Select](/docs/react/components/formatting-toolbar) item for turning a block into a diagram, to spread alongside the defaults. +- `diagramLocales` translates the diagram strings — merge a locale into the editor's `dictionary` under the `diagram` key (see [Localization](/docs/features/localization)); without one, the bundled English strings are used. + +The example below wires them all up. + +## Example + + + +## Exporting + +Diagrams export to every format BlockNote supports. [Markdown](/docs/features/export/markdown) works out of the box — diagrams export as ` ```mermaid ` fenced code blocks, their common Markdown notation. + +The [PDF](/docs/features/export/pdf), [DOCX](/docs/features/export/docx), [ODT](/docs/features/export/odt), and [email](/docs/features/export/email) exporters embed the diagram as an image via their mappings — they live as subpaths of this package, and each exports a `createDiagramBlockMapping` factory to spread into the exporter's default mappings. The [DOCX exporter](/docs/features/export/docx) shown here; the [PDF](/docs/features/export/pdf), [ODT](/docs/features/export/odt), and [email](/docs/features/export/email) exporters work the same way with their respective subpaths: + +```typescript +import { + DOCXExporter, + docxDefaultSchemaMappings, +} from "@blocknote/xl-docx-exporter"; +import { createDiagramBlockMapping } from "@blocknote/diagram-block/docx-exporter"; +// ...or "@blocknote/diagram-block/pdf-exporter", +// "@blocknote/diagram-block/odt-exporter", +// "@blocknote/diagram-block/email-exporter" + +const exporter = new DOCXExporter(editor.schema, { + ...docxDefaultSchemaMappings, + blockMapping: { + ...docxDefaultSchemaMappings.blockMapping, + diagram: createDiagramBlockMapping(), + }, +}); +``` + +The factory takes one option: + +```typescript +createDiagramBlockMapping(options?: { + /** + * Renders the Mermaid source to an image. Defaults to the built-in + * Mermaid renderer, which only works in the browser - see "Exporting + * server-side" below. + */ + renderDiagram?: RenderDiagram; +}); +``` + +Invalid Mermaid sources render an error placeholder identifying the offending source, mirroring the editor. + +The email subpath's factory additionally takes an `imageDelivery` option: some email clients don't display the default data URL images, and the generated images can be delivered as inline `cid:` attachments instead — see [image delivery](/docs/features/export/email#math--diagram-blocks) on the email page. + +### Exporting server-side + +Rendering Mermaid source to an image requires a browser, so the built-in renderer only works for client-side exports. When exporting server-side, pass a `renderDiagram` function to `createDiagramBlockMapping` — without one, a server-side export throws: + +```typescript +import { createDiagramBlockMapping } from "@blocknote/diagram-block/docx-exporter"; +import type { RenderDiagram } from "@blocknote/diagram-block/docx-exporter"; + +const renderDiagram: RenderDiagram = async (source) => { + // Render the Mermaid source to an image with your renderer of choice. + return { + image: { data: pngBytes, mimeType: "image/png", width, height }, + }; +}; + +createDiagramBlockMapping({ renderDiagram }); +``` + +Common choices for the server-side renderer are [`@mermaid-js/mermaid-cli`](https://github.com/mermaid-js/mermaid-cli) (renders in a headless browser) or a [Kroki](https://kroki.io) server. Invalid Mermaid source is an expected failure — return it as `{ error }` rather than throwing, and the export renders the error placeholder for that block instead of failing. diff --git a/docs/content/docs/features/blocks/math.mdx b/docs/content/docs/features/blocks/math.mdx new file mode 100644 index 0000000000..048686f2b8 --- /dev/null +++ b/docs/content/docs/features/blocks/math.mdx @@ -0,0 +1,192 @@ +--- +title: Math & Equations +description: LaTeX math blocks and inline math for BlockNote — rendered as formulas in the editor, and exportable to Markdown, PDF, DOCX, ODT, and email. +--- + +# Math & Equations + +The `@blocknote/math-block` package adds mathematical notation to your documents: a **math block** for standalone equations and **inline math** that flows with the surrounding text. Both are authored as LaTeX in a source popup and rendered as formulas — [KaTeX](https://katex.org/) converts the LaTeX to MathML, which browsers display natively. + + + This block is only available in React (`@blocknote/react`). + + +```bash +npm install @blocknote/math-block +``` + +## Adding to your editor + +The package exports `createReactMathBlockSpec` (the block) and `createReactInlineMathSpec` (the inline content). Add them to your schema's `blockSpecs` and `inlineContentSpecs` respectively: + +```tsx +import { BlockNoteSchema } from "@blocknote/core"; +import { + createReactMathBlockSpec, + createReactInlineMathSpec, +} from "@blocknote/math-block"; + +const schema = BlockNoteSchema.create().extend({ + blockSpecs: { + // Adds the Math block to the schema. + mathBlock: createReactMathBlockSpec(), + }, + inlineContentSpecs: { + // Adds the inline Math content to the schema. + math: createReactInlineMathSpec(), + }, +}); +``` + +To highlight the LaTeX source in the popup, add the [syntax highlighting](/docs/features/blocks/code-blocks#syntax-highlighting) extension to your editor. The math block and inline math already declare their source language (`latex`), so no per-block configuration is needed: + +```tsx +import { syntaxHighlighter } from "@blocknote/code-block"; + +const editor = useCreateBlockNote({ + schema, + extensions: [syntaxHighlighter], +}); +``` + +## Menu items & localization + +Because the math specs live in an optional package, their editor integrations are opt-in too — the package exports everything needed: + +```tsx +import { + getMathSlashMenuItems, // Slash Menu items for inserting math + getMathBlockTypeSelectItems, // Block Type Select item for the Formatting Toolbar + locales as mathLocales, // dictionary strings, merged under the `math` key +} from "@blocknote/math-block"; +``` + +- `getMathSlashMenuItems(editor)` returns [Slash Menu](/docs/react/components/suggestion-menus#slash-menu) items for inserting a math block or inline math — combine them with the default items via `combineByGroup`. +- `getMathBlockTypeSelectItems(editor)` returns a [Block Type Select](/docs/react/components/formatting-toolbar) item for turning a block into a math block, to spread alongside the defaults. +- `mathLocales` translates the math strings — merge a locale into the editor's `dictionary` under the `math` key (see [Localization](/docs/features/localization)); without one, the bundled English strings are used. + +The example below wires them all up. + +## Example + + + +## Exporting + +Math exports to every format BlockNote supports: + +- **HTML** works out of the box — the export produces a native MathML `` element (with the LaTeX embedded for lossless round-trips), and pasting MathML back in converts to LaTeX. +- **[Markdown](/docs/features/export/markdown)** also works out of the box — math blocks export as `$$` blocks and inline math as `$...$` spans, their common Markdown notations. +- **PDF, DOCX, ODT, and email** use exporter mappings, which live as subpaths of this package — spread them into the default mappings of the exporter you use, as shown per format below. Invalid LaTeX renders an error placeholder identifying the offending source, mirroring the editor. + +### DOCX + +With the [DOCX exporter](/docs/features/export/docx), math exports as native (editable) Word equations. Works server-side out of the box — the LaTeX is converted to OMML without rendering: + +```typescript +import { + DOCXExporter, + docxDefaultSchemaMappings, +} from "@blocknote/xl-docx-exporter"; +import { + inlineMathMapping, + mathBlockMapping, +} from "@blocknote/math-block/docx-exporter"; + +const exporter = new DOCXExporter(editor.schema, { + ...docxDefaultSchemaMappings, + blockMapping: { + ...docxDefaultSchemaMappings.blockMapping, + mathBlock: mathBlockMapping, + }, + inlineContentMapping: { + ...docxDefaultSchemaMappings.inlineContentMapping, + math: inlineMathMapping, + }, +}); +``` + +### ODT + +With the [ODT exporter](/docs/features/export/odt), math exports as native (editable) formula objects. Also works server-side out of the box (LaTeX is converted to MathML without rendering): + +```typescript +import { + inlineMathMapping, + mathBlockMapping, +} from "@blocknote/math-block/odt-exporter"; + +// Spread into the ODTExporter's mappings exactly as for DOCX above. +``` + +### PDF + +With the [PDF exporter](/docs/features/export/pdf), math blocks export as vector formulas — no rasterization, so they also work server-side out of the box. Inline math is rasterized to images that flow with the text: + +```typescript +import { + createInlineMathMapping, + mathBlockMapping, +} from "@blocknote/math-block/pdf-exporter"; + +// Spread into the PDFExporter's mappings as for DOCX above - note that +// inline math is a factory here: `math: createInlineMathMapping()`. +``` + +The inline math factory takes one option: + +```typescript +createInlineMathMapping(options?: { + /** + * Rasterizes the formula SVG to an image. Defaults to the built-in + * canvas rasterizer, which only works in the browser - when exporting + * server-side, pass one backed by e.g. `@resvg/resvg-js` or `sharp`; + * without it, a server-side export throws. The `RasterizeSVG` type is + * exported from the same subpath. + */ + rasterize?: RasterizeSVG; +}); +``` + +Math blocks require the `@react-pdf/math` package (a peer dependency of the PDF mapping). + +### Email + +With the [email exporter](/docs/features/export/email), math exports as images with the LaTeX source as the alt text: math blocks are rasterized to PNG in the browser (and embedded as SVG elsewhere), inline math is always embedded as SVG: + +```typescript +import { + createInlineMathMapping, + createMathBlockMapping, +} from "@blocknote/math-block/email-exporter"; + +// Spread into the ReactEmailExporter's mappings as for DOCX above - both +// are factories here: `mathBlock: createMathBlockMapping()` and +// `math: createInlineMathMapping()`. +``` + +Both factories take delivery-related options: + +```typescript +createMathBlockMapping(options?: { + /** + * Rasterizes the formula SVG to a raster image. Defaults to the built-in + * canvas rasterizer in the browser; elsewhere (e.g. server-side email + * rendering at send time), the formula is embedded as an SVG instead - + * pass a rasterizer (e.g. backed by `@resvg/resvg-js` or `sharp`) to get + * PNGs there, which more email clients display. + */ + rasterize?: RasterizeSVG; + /** + * How generated images get into the email: embedded as data URLs by + * default, or as inline `cid:` attachments - see the email exporter's + * image delivery docs. + */ + imageDelivery?: ReactEmailImageDelivery; +}); + +// `createInlineMathMapping` takes the same `imageDelivery` option (inline +// math is always SVG, so there's no `rasterize`). +``` + +Some email clients don't display data URL images — see [image delivery](/docs/features/export/email#math--diagram-blocks) on the email page for delivering the generated images as inline `cid:` attachments instead. diff --git a/docs/content/docs/features/blocks/meta.json b/docs/content/docs/features/blocks/meta.json index cf603446fc..3ce8d80a91 100644 --- a/docs/content/docs/features/blocks/meta.json +++ b/docs/content/docs/features/blocks/meta.json @@ -6,6 +6,8 @@ "tables", "embeds", "code-blocks", + "math", + "diagrams", "inline-content", "custom", "..." diff --git a/docs/content/docs/features/custom-schemas/custom-blocks.mdx b/docs/content/docs/features/custom-schemas/custom-blocks.mdx index 60bacafe68..ff25cf838c 100644 --- a/docs/content/docs/features/custom-schemas/custom-blocks.mdx +++ b/docs/content/docs/features/custom-schemas/custom-blocks.mdx @@ -54,18 +54,22 @@ The Block Config describes the shape of your custom blocks. Use it to specify th ```typescript type BlockConfig = { type: string; - content: "inline" | "none"; + content: "inline" | "plain" | "none"; readonly propSchema: PropSchema; }; ``` `type:` Defines the identifier of the custom block. -`content:` `inline` if your custom block should support rich text content, `none` if not. +`content:` Defines what kind of editable content the block holds: + +- `"inline"` for rich text content, i.e. [inline content](/docs/foundations/document-structure#inline-content-objects) such as styled text and links. +- `"plain"` for unstyled plain text, like a code block's source. Plain content can't hold formatting marks (bold, italic, etc.) or other inline content, and it's the only content type that supports [syntax highlighting](/docs/features/blocks/code-blocks#syntax-highlighting). +- `"none"` if the block holds no editable content. - _In the alert demo, we want the user to be able to type text in our alert, so - we set `content` to `"inline"`._ + _In the alert demo, we want the user to be able to type rich text in our + alert, so we set `content` to `"inline"`._ `propSchema:` The `PropSchema` specifies the props that the block supports. Block props (properties) are data stored with your Block in the document, and can be used to customize its appearance or behavior. @@ -124,6 +128,10 @@ type ReactCustomBlockImplementation = { contest: { nestingLevel: number }; }>; parse?: (element: HTMLElement) => PartialBlock["props"] | undefined; + parseContent?: (options: { + el: HTMLElement; + schema: Schema; + }) => Fragment | undefined; runsBefore?: string[]; meta?: { hardBreakShortcut?: "shift+enter" | "enter" | "none"; @@ -132,6 +140,7 @@ type ReactCustomBlockImplementation = { code?: boolean; defining?: boolean; isolating?: boolean; + highlight?: (block: Block) => string | undefined; }; }; ``` @@ -142,7 +151,7 @@ type ReactCustomBlockImplementation = { - `editor:` The BlockNote editor instance that the block is in. -- `contentRef:` A React `ref` you can use to mark which element in your block is editable, this is only available if your block config contains `content: "inline"`. +- `contentRef:` A React `ref` you can use to mark which element in your block is editable. This is only available if your block config contains `content: "inline"` or `content: "plain"`. `toExternalHTML?:` This component is used whenever the block is being exported to HTML for use outside BlockNote, for example when copying it to the clipboard. If it's not defined, BlockNote will just use `render` for the HTML conversion. Takes the same props as `render` and an additional `context` prop, which is. an object with the following attributes: @@ -158,11 +167,13 @@ type ReactCustomBlockImplementation = { - `element`: The HTML element that's being parsed. +`parseContent?:` An advanced option used in conjunction with `parse` that allows for more control when converting HTML into blocks. Runs when `parse` returns non-undefined and corresponds to [getContent](https://prosemirror.net/docs/ref/#model.TagParseRule.getContent) in the ProseMirror API. + `runsBefore?:` If this block has parsing or extensions that need to be given priority over any other blocks, you can pass their `type`s in an array here. `meta?:` An object for setting various generic properties of the block. -- `hardBreakShortcut?:` Defines which keyboard shortcut should be used to insert a hard break into the block's inline content. Defaults to `"shift+enter"`. +- `hardBreakShortcut?:` Defines which keyboard shortcut should be used to insert a hard break into the block's inline content. Defaults to `"shift+enter"`. For `content: "plain"` blocks (which can't hold hard break nodes), the shortcut inserts a literal newline (`"\n"`) instead. - `selectable?:` Can be set to false in order to make the block non-selectable, both using the mouse and keyboard. This also helps with being able to select non-editable content within the block. Should only be set to false when `content` is `none` and defaults to true. @@ -174,6 +185,8 @@ type ReactCustomBlockImplementation = { - `isolating?:` Whether this block is [isolating](https://prosemirror.net/docs/ref/#model.NodeSpec.isolating). +- `highlight?:` A callback that returns the language the block's text should be syntax-highlighted as (or `undefined` for no highlighting). Highlighting only takes effect when the [syntax highlighting extension](/docs/features/blocks/code-blocks#syntax-highlighting) is added to the editor. + ### Block Extensions While the example on this page doesn't use it, `createReactBlockSpec` takes a third, optional argument `extensions`. This is for adding editor `extensions` that are specific to the block, which you can find out more about [here](/docs/features/extensions). diff --git a/docs/content/docs/features/custom-schemas/custom-inline-content.mdx b/docs/content/docs/features/custom-schemas/custom-inline-content.mdx index efc8c424ae..5c97fcae72 100644 --- a/docs/content/docs/features/custom-schemas/custom-inline-content.mdx +++ b/docs/content/docs/features/custom-schemas/custom-inline-content.mdx @@ -50,14 +50,18 @@ The Inline Content Config describes the shape of your custom inline content. Use ```typescript type CustomInlineContentConfig = { type: string; - content: "styled" | "none"; + content: "styled" | "plain" | "none"; readonly propSchema: PropSchema; }; ``` `type:` Defines the identifier of the custom inline content. -`content:` `styled` if your custom inline content should contain [`StyledText`](/docs/foundations/document-structure#inline-content-objects), `none` if not. +`content:` Defines what kind of editable content the inline content holds: + +- `"styled"` for rich text, i.e. [`StyledText`](/docs/foundations/document-structure#inline-content-objects). +- `"plain"` for unstyled plain text, like an inline code or inline math source. Plain content can't hold formatting marks (bold, italic, etc.), and it's the only content type that supports [syntax highlighting](/docs/features/blocks/code-blocks#syntax-highlighting). +- `"none"` if the inline content holds no editable content. _In the mentions demo, we want each mention to be a single, non-editable @@ -130,7 +134,7 @@ type ReactCustomInlineContentImplementation = { - `inlineContent:` The inline content that should be rendered. Its type and props will match the type and PropSchema defined in the Inline Content Config. -- `contentRef:` A React `ref` you can use to mark which element in your inline content is editable, this is only available if your inline content config contains `content: "styled"`. +- `contentRef:` A React `ref` you can use to mark which element in your inline content is editable, this is only available if your inline content config contains `content: "styled"` or `content: "plain"`. - `draggable:` Specifies whether the inline content can be dragged within the editor. If set to `true`, the inline content will be draggable. Defaults to `false` if not specified. If this is true, you should add `data-drag-handle` to the DOM element that should function as the drag handle. diff --git a/docs/content/docs/features/custom-schemas/source-with-preview.mdx b/docs/content/docs/features/custom-schemas/source-with-preview.mdx new file mode 100644 index 0000000000..03dd18c51e --- /dev/null +++ b/docs/content/docs/features/custom-schemas/source-with-preview.mdx @@ -0,0 +1,108 @@ +--- +title: Source with Preview Blocks +description: Build custom blocks and inline content that are authored as source code but rendered as a preview — like BlockNote's math and diagram blocks. +--- + +# Source with Preview Blocks + +Some blocks are authored as source code but are more useful shown as the thing that code produces — a LaTeX formula rendered as a formula, or Mermaid source rendered as a diagram. Unlike a [code block](/docs/features/blocks/code-blocks), these blocks show the rendered preview in place, while the source is edited in a popup. + + + The components on this page are only available in React + (`@blocknote/react`). + + +BlockNote's [math](/docs/features/blocks/math) and [diagram](/docs/features/blocks/diagrams) blocks are built on this pattern, and the same building blocks are available to create your own: + +- `SourceBlockWithPreview` (from `@blocknote/react`) — for [custom blocks](/docs/features/custom-schemas/custom-blocks). +- `SourceInlineContentWithPreview` (from `@blocknote/react`) — for [custom inline content](/docs/features/custom-schemas/custom-inline-content). + +Both render the preview you give them in place of the block/inline content, and manage the editable source popup for you. The popup behavior itself is driven by editor-wide extensions that BlockNote registers by default — a spec opts in simply by setting `hasPreview: true` in its `meta`. + +## Custom Block + +Three pieces make a source-with-preview block — the [example below](#example) implements them all: + +**1. A block config with [`"plain"` content](/docs/features/custom-schemas/custom-blocks)** — the source is stored as the block's plain text content: + +```tsx +const createMyBlockConfig = createBlockConfig( + () => + ({ + type: "myBlock" as const, + propSchema: {}, + content: "plain" as const, + }) as const, +); +``` + +**2. A render component** that reads the source, renders it however you like, and hands the result to `SourceBlockWithPreview`: + +```tsx +// The block's content as plain text, i.e. the source to render. +const source = plainContentToString(props.block.content).trim(); +// Your own rendering, returning a preview element or an error for invalid +// source - the example below renders CSV to a table. +const { preview, error } = renderMySource(source); + +return ( + +); +``` + +A few more props customize the states: `errorPreview` for the compact error state shown in place of the preview, `emptySourcePlaceholder` for when the source is empty (a string customizes the default placeholder's text, an element — e.g. the exported `PreviewPlaceholder` with your own icon — replaces it entirely), and `sourcePlaceholder` for the popup input's placeholder. See the `SourceWithPreviewProps` type for the full list. + +**3. The spec's `meta`**, opting into the popup: + +```tsx +const createMyBlockSpec = createReactBlockSpec(createMyBlockConfig, { + meta: { + code: true, + // Marks the block as rendering a preview with an editable source popup. + hasPreview: true, + // What Enter does while the popup is open: "enter" inserts a newline + // (multiline sources, like diagrams), "shift+enter" closes the popup + // (single-line sources, like math). + hardBreakShortcut: "enter", + }, + render: MyBlockPreview, +}); +``` + +Because the block uses `"plain"` content, you can also syntax-highlight the source in the popup (as the math and diagram blocks do): add a `highlight` callback to the `meta` that returns the source language, then add the [syntax highlighting](/docs/features/blocks/code-blocks#syntax-highlighting) extension to your editor. + +## Custom Inline Content + +Inline content works the same way, with two differences: the component takes the inline-content render props (`node`, `getPos`), and for `"plain"` inline content the source is already a plain string: + +```tsx + +``` + +The spec is created with `createReactInlineContentSpec`, opting in via `meta: { code: true, hasPreview: true }`. Unlike blocks — which toggle the popup on click — inline content opens its popup exactly while the selection is inside its source, so it's always shown when selected. + +## Example + +A complete implementation of both — a CSV table block and a color chip inline content: + + + +The `@blocknote/math-block` and `@blocknote/diagram-block` packages are production implementations of the same pattern. diff --git a/docs/content/docs/features/export/docx.mdx b/docs/content/docs/features/export/docx.mdx index 2536daa6bb..aede75175d 100644 --- a/docs/content/docs/features/export/docx.mdx +++ b/docs/content/docs/features/export/docx.mdx @@ -110,6 +110,10 @@ new DOCXExporter(schema, { }); ``` +### Math & diagram blocks + +The [math](/docs/features/blocks/math) and [diagram](/docs/features/blocks/diagrams) blocks ship their own DOCX mappings — math exports as native (editable) Word equations, diagrams as embedded images. See [exporting math](/docs/features/blocks/math#docx) and [exporting diagrams](/docs/features/blocks/diagrams#exporting) for the setup. + ### Exporter options The `DOCXExporter` constructor takes an optional `options` parameter. @@ -120,6 +124,10 @@ const defaultOptions = { // a function to resolve external resources in order to avoid CORS issues // by default, this calls a BlockNote hosted server-side proxy to resolve files resolveFileUrl: corsProxyResolveFileUrl, + // the strings rendered into the exported document (file link texts, error + // placeholders); pass a locale from @blocknote/core/locales (or your + // editor's dictionary) to export in another language + dictionary: locales.en, // the colors to use in the Docx for things like highlighting, background colors and font colors. colors: COLORS_DEFAULT, // defaults from @blocknote/core }; diff --git a/docs/content/docs/features/export/email.mdx b/docs/content/docs/features/export/email.mdx index b36f7ccd58..0c8f18ccc1 100644 --- a/docs/content/docs/features/export/email.mdx +++ b/docs/content/docs/features/export/email.mdx @@ -126,6 +126,10 @@ const defaultOptions = { // a function to resolve external resources in order to avoid CORS issues // by default, this calls a BlockNote hosted server-side proxy to resolve files resolveFileUrl: corsProxyResolveFileUrl, + // the strings rendered into the exported document (file link texts, error + // placeholders); pass a locale from @blocknote/core/locales (or your + // editor's dictionary) to export in another language + dictionary: locales.en, // the colors to use in the email for things like highlighting, background colors and font colors. colors: COLORS_DEFAULT, // defaults from @blocknote/core }; @@ -168,3 +172,43 @@ new ReactEmailExporter(schema, { styleMapping, }); ``` + +### Math & diagram blocks + +The [math](/docs/features/blocks/math) and [diagram](/docs/features/blocks/diagrams) blocks ship their own email mappings, exporting as images with the LaTeX/Mermaid source as the alt text. See [exporting math](/docs/features/blocks/math#email) and [exporting diagrams](/docs/features/blocks/diagrams#exporting) for the setup. + +**Image delivery.** By default the images are embedded as data URLs — self-contained, but some email clients (notably Gmail and Outlook for Windows) don't display data URL images and show the alt text instead. For those, deliver the images as inline `cid:` attachments (the most widely supported way to embed generated images) and pass the collected attachments to your mailer at send time: + +```typescript +import { + ReactEmailExporter, + reactEmailDefaultSchemaMappings, + createCIDImageDelivery, +} from "@blocknote/xl-email-exporter"; +import { + createInlineMathMapping, + createMathBlockMapping, +} from "@blocknote/math-block/email-exporter"; +import { createDiagramBlockMapping } from "@blocknote/diagram-block/email-exporter"; + +const imageDelivery = createCIDImageDelivery(); +const exporter = new ReactEmailExporter(editor.schema, { + ...reactEmailDefaultSchemaMappings, + blockMapping: { + ...reactEmailDefaultSchemaMappings.blockMapping, + mathBlock: createMathBlockMapping({ imageDelivery }), + diagram: createDiagramBlockMapping({ imageDelivery }), + }, + inlineContentMapping: { + ...reactEmailDefaultSchemaMappings.inlineContentMapping, + math: createInlineMathMapping({ imageDelivery }), + }, +}); + +const html = await exporter.toReactEmailDocument(editor.document); + +// e.g. with nodemailer (works the same with other mailers): +await transporter.sendMail({ html, attachments: imageDelivery.attachments }); +``` + +**Server-side rendering.** Emails are usually rendered server-side at send time — see [exporting math](/docs/features/blocks/math#email) and [exporting diagrams server-side](/docs/features/blocks/diagrams#exporting-server-side) for what that needs. diff --git a/docs/content/docs/features/export/odt.mdx b/docs/content/docs/features/export/odt.mdx index 3f5248a875..b5c35fc243 100644 --- a/docs/content/docs/features/export/odt.mdx +++ b/docs/content/docs/features/export/odt.mdx @@ -85,6 +85,10 @@ new ODTExporter(schema, { }); ``` +### Math & diagram blocks + +The [math](/docs/features/blocks/math) and [diagram](/docs/features/blocks/diagrams) blocks ship their own ODT mappings — math exports as native (editable) formula objects, diagrams as embedded images. See [exporting math](/docs/features/blocks/math#odt) and [exporting diagrams](/docs/features/blocks/diagrams#exporting) for the setup. + ### Exporter options The `ODTExporter` constructor takes an optional `options` parameter. @@ -95,6 +99,10 @@ const defaultOptions = { // a function to resolve external resources in order to avoid CORS issues // by default, this calls a BlockNote hosted server-side proxy to resolve files resolveFileUrl: corsProxyResolveFileUrl, + // the strings rendered into the exported document (file link texts, error + // placeholders); pass a locale from @blocknote/core/locales (or your + // editor's dictionary) to export in another language + dictionary: locales.en, // the colors to use in the ODT for things like highlighting, background colors and font colors. colors: COLORS_DEFAULT, // defaults from @blocknote/core }; diff --git a/docs/content/docs/features/export/pdf.mdx b/docs/content/docs/features/export/pdf.mdx index 08594cf96f..ffea275c0f 100644 --- a/docs/content/docs/features/export/pdf.mdx +++ b/docs/content/docs/features/export/pdf.mdx @@ -83,6 +83,10 @@ new PDFExporter(schema, { }); ``` +### Math & diagram blocks + +The [math](/docs/features/blocks/math) and [diagram](/docs/features/blocks/diagrams) blocks ship their own PDF mappings — math blocks export as vector formulas, inline math as images flowing with the text, diagrams as embedded images. See [exporting math](/docs/features/blocks/math#pdf) and [exporting diagrams](/docs/features/blocks/diagrams#exporting) for the setup. + ### Exporter options The `PDFExporter` constructor takes an optional `options` parameter. @@ -99,6 +103,10 @@ const defaultOptions = { // a function to resolve external resources in order to avoid CORS issues // by default, this calls a BlockNote hosted server-side proxy to resolve files resolveFileUrl: corsProxyResolveFileUrl, + // the strings rendered into the exported document (file link texts, error + // placeholders); pass a locale from @blocknote/core/locales (or your + // editor's dictionary) to export in another language + dictionary: locales.en, // the colors to use in the PDF for things like highlighting, background colors and font colors. colors: COLORS_DEFAULT, // defaults from @blocknote/core }; diff --git a/docs/content/docs/foundations/document-structure.mdx b/docs/content/docs/foundations/document-structure.mdx index 736671dae3..e63e4c05ef 100644 --- a/docs/content/docs/foundations/document-structure.mdx +++ b/docs/content/docs/foundations/document-structure.mdx @@ -68,7 +68,7 @@ type StyledText = { type CustomInlineContent = { type: string; - content: StyledText[] | undefined; + content: StyledText[] | string | undefined; props: Record; }; @@ -91,7 +91,31 @@ The demo below shows the editor contents (document) in JSON. It's an array of `B ## Special Cases -While most blocks use an array of `InlineContent` objects to describe their content (e.g.: paragraphs, headings, list items), some blocks, like [images](/docs/features/blocks/embeds#image), don't contain any rich text content, so their `content` fields will be `undefined`. +While most blocks use an array of `InlineContent` objects to describe their content (e.g.: paragraphs, headings, list items), some blocks, like [images](/docs/features/blocks/embeds#image), don't contain any rich text content, so their `content` fields will be `undefined`. There are a few other cases where `content` will not contain `InlineContent`. + +### Plain Text Content + +Some blocks, like [code blocks](/docs/features/blocks/code-blocks), store their content as plain text rather than rich text. Their `content` is still an array of `StyledText` objects, but the text is always unstyled (its `styles` is an empty object) and can't contain links or other inline content: + +```typescript +type PlainContent = { + type: "text"; + text: string; + styles: {}; +}[]; +``` + +To read a plain block's text, use `plainContentToString`: + +```typescript +import { plainContentToString } from "@blocknote/core"; + +const text = plainContentToString(block.content); +``` + +[Custom inline content](/docs/features/custom-schemas/custom-inline-content) can hold plain text too, but there it's represented directly as a `string` rather than an array. + +Custom blocks and inline content opt into plain text content by setting `content: "plain"` - see [Custom Blocks](/docs/features/custom-schemas/custom-blocks) and [Custom Inline Content](/docs/features/custom-schemas/custom-inline-content). ### Column Blocks diff --git a/docs/next.config.ts b/docs/next.config.ts index 0879257822..fafa53592a 100644 --- a/docs/next.config.ts +++ b/docs/next.config.ts @@ -10,6 +10,12 @@ const config = { reactStrictMode: true, serverExternalPackages: ["typescript", "twoslash"], reactCompiler: true, + // TypeScript 7 ships only the native `tsc` binary; it no longer exposes the + // JS compiler API that Next's in-process type check uses. Shell out to the + // CLI instead. + experimental: { + useTypeScriptCli: true, + }, redirects, images: { remotePatterns: [ diff --git a/docs/package.json b/docs/package.json index 18b6792b6f..a2771cd1cf 100644 --- a/docs/package.json +++ b/docs/package.json @@ -8,6 +8,7 @@ "dev:email": "next dev", "prebuild:site": "vp run --filter @blocknote/dev-scripts gen", "build:site": "fumadocs-mdx && NODE_OPTIONS='--max-old-space-size=6144' next build", + "build:vercel": "cd .. && pnpm exec vp run --filter 'docs...' build && pnpm --filter @blocknote/dev-scripts run gen && cd docs && fumadocs-mdx && NODE_OPTIONS='--max-old-space-size=6144' next build", "start": "next start", "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", "postinstall": "[ -n \"$SKIP_DOCS_POSTINSTALL\" ] || fumadocs-mdx", @@ -22,7 +23,9 @@ "@blocknote/ariakit": "workspace:*", "@blocknote/code-block": "workspace:*", "@blocknote/core": "workspace:*", + "@blocknote/diagram-block": "workspace:*", "@blocknote/mantine": "workspace:*", + "@blocknote/math-block": "workspace:*", "@blocknote/react": "workspace:*", "@blocknote/server-util": "workspace:*", "@blocknote/shadcn": "workspace:*", @@ -32,6 +35,7 @@ "@blocknote/xl-multi-column": "workspace:*", "@blocknote/xl-odt-exporter": "workspace:*", "@blocknote/xl-pdf-exporter": "workspace:*", + "@floating-ui/react": "^0.27.18", "@fumadocs/base-ui": "16.5.0", "@liveblocks/client": "^3.19.5", "@liveblocks/react": "^3.19.5", @@ -48,13 +52,14 @@ "@polar-sh/sdk": "^0.42.2", "@react-email/components": "^1.0.4", "@react-email/render": "^2.0.4", - "@react-pdf/renderer": "^4.3.0", + "@react-pdf/math": "^2.0.1", + "@react-pdf/renderer": "^4.5.1", "@sentry/nextjs": "^10.34.0", - "@shikijs/core": "^4", - "@shikijs/engine-javascript": "^4", - "@shikijs/langs-precompiled": "^4", - "@shikijs/themes": "^4", - "@shikijs/types": "^4", + "@shikijs/core": "^4.4.3", + "@shikijs/engine-javascript": "^4.4.3", + "@shikijs/langs-precompiled": "^4.4.3", + "@shikijs/themes": "^4.4.3", + "@shikijs/types": "^4.4.3", "@tiptap/core": "^3.29.2", "@uppy/core": "^3.13.1", "@uppy/dashboard": "^3.9.1", @@ -69,19 +74,28 @@ "@uppy/xhr-upload": "^3.4.0", "@vercel/analytics": "^1.6.1", "@y-sweet/react": "^0.6.3", + "@y/prosemirror": "^2.0.0-6", + "@y/protocols": "^1.0.6-rc.1", + "@y/websocket": "^4.0.0-3", + "@y/y": "^14.0.0-rc.23", "ai": "^6.0.5", "better-auth": "~1.4.15", "better-sqlite3": "^12.6.2", "class-variance-authority": "^0.7.1", + "docx": "^9.6.1", "framer-motion": "^12.26.2", "fumadocs-core": "16.5.0", "fumadocs-mdx": "^14.2.6", "fumadocs-twoslash": "^3.1.12", "fumadocs-typescript": "^5.1.1", "fumadocs-ui": "npm:@fumadocs/base-ui@16.5.0", + "katex": "^0.16.11", + "lib0": "1.0.0-rc.22", "lucide-react": "^0.562.0", + "mathjax-full": "^3.2.2", + "mermaid": "^11.0.0", "motion": "^12.28.1", - "next": "^16.2.7", + "next": "^16.3.0", "next-themes": "^0.4.6", "nodemailer": "^7.0.12", "pg": "^8.17.1", @@ -92,18 +106,13 @@ "react-icons": "^5.5.0", "react-use-measure": "^2.1.7", "scroll-into-view-if-needed": "^3.1.0", - "shiki": "^4", + "shiki": "^4.4.3", "tailwind-merge": "^3.4.0", + "typescript-5": "npm:typescript@^5.9.3", "y-partykit": "^0.0.25", + "y-websocket": "^2.1.0", "yjs": "^13.6.27", - "zod": "^4.3.5", - "@y/protocols": "^1.0.6-rc.1", - "@y/websocket": "^4.0.0-3", - "@y/y": "^14.0.0-rc.23", - "@y/prosemirror": "^2.0.0-6", - "@floating-ui/react": "^0.27.18", - "lib0": "1.0.0-rc.22", - "y-websocket": "^2.1.0" + "zod": "^4.3.5" }, "devDependencies": { "@blocknote/code-block": "workspace:*", @@ -132,6 +141,6 @@ "serve": "^14.2.6", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3" + "typescript": "^7.0.2" } } diff --git a/docs/source.config.ts b/docs/source.config.ts index b67079e70b..051d006bde 100644 --- a/docs/source.config.ts +++ b/docs/source.config.ts @@ -1,3 +1,5 @@ +import { createRequire } from "node:module"; +import path from "node:path"; import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins"; import { defineConfig, @@ -7,8 +9,44 @@ import { } from "fumadocs-mdx/config"; import { transformerTwoslash } from "fumadocs-twoslash"; import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs"; +import ts from "typescript-5"; import { z } from "zod/v4"; +// `twoslash` (which type-checks the code samples in our MDX) needs the classic +// TypeScript JavaScript API - `ts.sys`, `createLanguageService`, and friends. +// TypeScript 7 does not ship one: its `typescript` package only exposes +// `{ version }` plus a native `tsc` binary, so the `import ts from "typescript"` +// inside twoslash yields `undefined` for `ts.sys` and the docs build fails while +// highlighting the first `twoslash` code fence. +// +// We can't just add `typescript@5` to this package: `vite-plus`/`vitest` take +// TypeScript as an (optional, transitive) peer, so a second TypeScript version +// in a workspace importer splits them into multiple physical instances, and a +// package that lands on the other instance gets a second `SnapshotClient` - +// every `toMatchFileSnapshot` then fails with "The snapshot state for '...' is +// not found". (`pnpm-workspace.yaml` pins `@types/node` and `jsdom` for the same +// reason.) An `npm:` alias is invisible to pnpm's peer resolution, so it gives +// us a TypeScript 5 that can never enter a `vitest` peer key. We hand it to +// twoslash explicitly below; `typescript` itself stays on 7 for `tsc --noEmit` +// and Next's `experimental.useTypeScriptCli`. +// +// Once twoslash supports the TypeScript 7 API (per the TypeScript 7 release +// notes, an API is expected in 7.1), drop the alias and this wiring. +const require = createRequire(import.meta.url); +// Resolves to `.../typescript/lib/typescript.js`; twoslash passes the directory +// to `@typescript/vfs` to find `lib.*.d.ts`. Without it the lib files are looked +// up next to the ambient (TypeScript 7) package, where they don't exist. +const tsLibDirectory = path.dirname(require.resolve("typescript-5")); +// twoslash types this option as `typeof import("typescript")`, which here is the +// TypeScript 7 stub - `{ version, versionMajorMinor, default }`. The aliased +// TypeScript 5 has everything twoslash actually calls, but no `default` export, +// so it fails a structural check against a shape that only looks like that +// because the ambient `typescript` is the package twoslash can't use anyway. +type TwoslashOptions = NonNullable< + NonNullable[0]>["twoslashOptions"] +>; +const tsModule = ts as unknown as TwoslashOptions["tsModule"]; + // You can customise Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.dev/docs/mdx/collections export const docs = defineDocs({ @@ -72,6 +110,7 @@ export default defineConfig({ ...(rehypeCodeDefaultOptions.transformers ?? []), transformerTwoslash({ typesCache: createFileSystemTypesCache(), + twoslashOptions: { tsModule, tsLibDirectory }, }), ], // important: Shiki doesn't support lazy loading languages for codeblocks in Twoslash popups diff --git a/docs/validate-links.mjs b/docs/validate-links.mjs index d9a45f69b2..15dcf00113 100644 --- a/docs/validate-links.mjs +++ b/docs/validate-links.mjs @@ -1,12 +1,18 @@ -import { getTableOfContents } from "fumadocs-core/content/toc"; -import { getSlugs } from "fumadocs-core/source"; -import { - printErrors, - readFiles, - scanURLs, - validateFiles, -} from "next-validate-link"; import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// The content globs below are cwd-relative, and the glob library captures +// `process.cwd()` when it is first imported - so pin the cwd to this +// script's directory *before* loading it (via dynamic imports; static +// imports would hoist above the chdir). Run from any other directory +// without this, the globs silently match zero files and report success. +process.chdir(path.dirname(fileURLToPath(import.meta.url))); + +const { getTableOfContents } = await import("fumadocs-core/content/toc"); +const { getSlugs } = await import("fumadocs-core/source"); +const { printErrors, readFiles, scanURLs, validateFiles } = + await import("next-validate-link"); + async function checkLinks() { const docsFiles = await readFiles("content/docs/**/*.{md,mdx}"); const pagesFiles = await readFiles("content/pages/**/*.{md,mdx}"); diff --git a/docs/vercel.json b/docs/vercel.json index e63fa7e15a..89b87238d0 100644 --- a/docs/vercel.json +++ b/docs/vercel.json @@ -1,5 +1,6 @@ { "cleanUrls": true, + "buildCommand": "corepack enable && pnpm run build:vercel", "installCommand": "cd .. && corepack enable && pnpm install", - "buildCommand": "cd .. && corepack enable && pnpm run build && pnpm --filter @blocknote/dev-scripts run gen && cd docs && pnpm exec fumadocs-mdx && NODE_OPTIONS='--max-old-space-size=6144' pnpm exec next build" + "ignoreCommand": "[ \"$VERCEL_GIT_COMMIT_REF\" = \"gh-pages\" ]" } diff --git a/examples/01-basic/01-minimal/tsconfig.json b/examples/01-basic/01-minimal/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/01-minimal/tsconfig.json +++ b/examples/01-basic/01-minimal/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/01-minimal/vite.config.ts b/examples/01-basic/01-minimal/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/01-minimal/vite.config.ts +++ b/examples/01-basic/01-minimal/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/02-block-objects/tsconfig.json b/examples/01-basic/02-block-objects/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/02-block-objects/tsconfig.json +++ b/examples/01-basic/02-block-objects/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/02-block-objects/vite.config.ts b/examples/01-basic/02-block-objects/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/02-block-objects/vite.config.ts +++ b/examples/01-basic/02-block-objects/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/03-multi-column/tsconfig.json b/examples/01-basic/03-multi-column/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/03-multi-column/tsconfig.json +++ b/examples/01-basic/03-multi-column/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/03-multi-column/vite.config.ts b/examples/01-basic/03-multi-column/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/03-multi-column/vite.config.ts +++ b/examples/01-basic/03-multi-column/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/04-default-blocks/tsconfig.json b/examples/01-basic/04-default-blocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/04-default-blocks/tsconfig.json +++ b/examples/01-basic/04-default-blocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/04-default-blocks/vite.config.ts b/examples/01-basic/04-default-blocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/04-default-blocks/vite.config.ts +++ b/examples/01-basic/04-default-blocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/05-removing-default-blocks/tsconfig.json b/examples/01-basic/05-removing-default-blocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/05-removing-default-blocks/tsconfig.json +++ b/examples/01-basic/05-removing-default-blocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/05-removing-default-blocks/vite.config.ts b/examples/01-basic/05-removing-default-blocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/05-removing-default-blocks/vite.config.ts +++ b/examples/01-basic/05-removing-default-blocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/06-block-manipulation/tsconfig.json b/examples/01-basic/06-block-manipulation/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/06-block-manipulation/tsconfig.json +++ b/examples/01-basic/06-block-manipulation/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/06-block-manipulation/vite.config.ts b/examples/01-basic/06-block-manipulation/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/06-block-manipulation/vite.config.ts +++ b/examples/01-basic/06-block-manipulation/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/07-selection-blocks/tsconfig.json b/examples/01-basic/07-selection-blocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/07-selection-blocks/tsconfig.json +++ b/examples/01-basic/07-selection-blocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/07-selection-blocks/vite.config.ts b/examples/01-basic/07-selection-blocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/07-selection-blocks/vite.config.ts +++ b/examples/01-basic/07-selection-blocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/08-ariakit/tsconfig.json b/examples/01-basic/08-ariakit/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/08-ariakit/tsconfig.json +++ b/examples/01-basic/08-ariakit/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/08-ariakit/vite.config.ts b/examples/01-basic/08-ariakit/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/08-ariakit/vite.config.ts +++ b/examples/01-basic/08-ariakit/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/09-shadcn/tsconfig.json b/examples/01-basic/09-shadcn/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/09-shadcn/tsconfig.json +++ b/examples/01-basic/09-shadcn/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/09-shadcn/vite.config.ts b/examples/01-basic/09-shadcn/vite.config.ts index 852c69b872..c990876056 100644 --- a/examples/01-basic/09-shadcn/vite.config.ts +++ b/examples/01-basic/09-shadcn/vite.config.ts @@ -17,6 +17,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/10-localization/tsconfig.json b/examples/01-basic/10-localization/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/10-localization/tsconfig.json +++ b/examples/01-basic/10-localization/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/10-localization/vite.config.ts b/examples/01-basic/10-localization/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/10-localization/vite.config.ts +++ b/examples/01-basic/10-localization/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/11-custom-placeholder/tsconfig.json b/examples/01-basic/11-custom-placeholder/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/11-custom-placeholder/tsconfig.json +++ b/examples/01-basic/11-custom-placeholder/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/11-custom-placeholder/vite.config.ts b/examples/01-basic/11-custom-placeholder/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/11-custom-placeholder/vite.config.ts +++ b/examples/01-basic/11-custom-placeholder/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/12-multi-editor/tsconfig.json b/examples/01-basic/12-multi-editor/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/12-multi-editor/tsconfig.json +++ b/examples/01-basic/12-multi-editor/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/12-multi-editor/vite.config.ts b/examples/01-basic/12-multi-editor/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/12-multi-editor/vite.config.ts +++ b/examples/01-basic/12-multi-editor/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/13-custom-paste-handler/tsconfig.json b/examples/01-basic/13-custom-paste-handler/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/13-custom-paste-handler/tsconfig.json +++ b/examples/01-basic/13-custom-paste-handler/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/13-custom-paste-handler/vite.config.ts b/examples/01-basic/13-custom-paste-handler/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/13-custom-paste-handler/vite.config.ts +++ b/examples/01-basic/13-custom-paste-handler/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/14-editor-scrollable/tsconfig.json b/examples/01-basic/14-editor-scrollable/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/14-editor-scrollable/tsconfig.json +++ b/examples/01-basic/14-editor-scrollable/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/14-editor-scrollable/vite.config.ts b/examples/01-basic/14-editor-scrollable/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/14-editor-scrollable/vite.config.ts +++ b/examples/01-basic/14-editor-scrollable/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/15-shadowdom/tsconfig.json b/examples/01-basic/15-shadowdom/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/15-shadowdom/tsconfig.json +++ b/examples/01-basic/15-shadowdom/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/15-shadowdom/vite.config.ts b/examples/01-basic/15-shadowdom/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/15-shadowdom/vite.config.ts +++ b/examples/01-basic/15-shadowdom/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/16-read-only-editor/tsconfig.json b/examples/01-basic/16-read-only-editor/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/16-read-only-editor/tsconfig.json +++ b/examples/01-basic/16-read-only-editor/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/16-read-only-editor/vite.config.ts b/examples/01-basic/16-read-only-editor/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/16-read-only-editor/vite.config.ts +++ b/examples/01-basic/16-read-only-editor/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/17-no-trailing-block/tsconfig.json b/examples/01-basic/17-no-trailing-block/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/17-no-trailing-block/tsconfig.json +++ b/examples/01-basic/17-no-trailing-block/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/17-no-trailing-block/vite.config.ts b/examples/01-basic/17-no-trailing-block/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/17-no-trailing-block/vite.config.ts +++ b/examples/01-basic/17-no-trailing-block/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/01-basic/testing/tsconfig.json b/examples/01-basic/testing/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/01-basic/testing/tsconfig.json +++ b/examples/01-basic/testing/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/01-basic/testing/vite.config.ts b/examples/01-basic/testing/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/01-basic/testing/vite.config.ts +++ b/examples/01-basic/testing/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/02-backend/01-file-uploading/tsconfig.json b/examples/02-backend/01-file-uploading/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/02-backend/01-file-uploading/tsconfig.json +++ b/examples/02-backend/01-file-uploading/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/02-backend/01-file-uploading/vite.config.ts b/examples/02-backend/01-file-uploading/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/02-backend/01-file-uploading/vite.config.ts +++ b/examples/02-backend/01-file-uploading/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/02-backend/02-saving-loading/tsconfig.json b/examples/02-backend/02-saving-loading/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/02-backend/02-saving-loading/tsconfig.json +++ b/examples/02-backend/02-saving-loading/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/02-backend/02-saving-loading/vite.config.ts b/examples/02-backend/02-saving-loading/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/02-backend/02-saving-loading/vite.config.ts +++ b/examples/02-backend/02-saving-loading/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/02-backend/03-s3/tsconfig.json b/examples/02-backend/03-s3/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/02-backend/03-s3/tsconfig.json +++ b/examples/02-backend/03-s3/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/02-backend/03-s3/vite.config.ts b/examples/02-backend/03-s3/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/02-backend/03-s3/vite.config.ts +++ b/examples/02-backend/03-s3/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/02-backend/04-rendering-static-documents/tsconfig.json b/examples/02-backend/04-rendering-static-documents/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/02-backend/04-rendering-static-documents/tsconfig.json +++ b/examples/02-backend/04-rendering-static-documents/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/02-backend/04-rendering-static-documents/vite.config.ts b/examples/02-backend/04-rendering-static-documents/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/02-backend/04-rendering-static-documents/vite.config.ts +++ b/examples/02-backend/04-rendering-static-documents/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/01-ui-elements-remove/tsconfig.json b/examples/03-ui-components/01-ui-elements-remove/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/01-ui-elements-remove/tsconfig.json +++ b/examples/03-ui-components/01-ui-elements-remove/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/01-ui-elements-remove/vite.config.ts b/examples/03-ui-components/01-ui-elements-remove/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/01-ui-elements-remove/vite.config.ts +++ b/examples/03-ui-components/01-ui-elements-remove/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/tsconfig.json b/examples/03-ui-components/02-formatting-toolbar-buttons/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/02-formatting-toolbar-buttons/tsconfig.json +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/vite.config.ts b/examples/03-ui-components/02-formatting-toolbar-buttons/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/02-formatting-toolbar-buttons/vite.config.ts +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts b/examples/03-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/04-side-menu-buttons/tsconfig.json b/examples/03-ui-components/04-side-menu-buttons/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/04-side-menu-buttons/tsconfig.json +++ b/examples/03-ui-components/04-side-menu-buttons/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/04-side-menu-buttons/vite.config.ts b/examples/03-ui-components/04-side-menu-buttons/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/04-side-menu-buttons/vite.config.ts +++ b/examples/03-ui-components/04-side-menu-buttons/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/tsconfig.json b/examples/03-ui-components/05-side-menu-drag-handle-items/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/05-side-menu-drag-handle-items/tsconfig.json +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/vite.config.ts b/examples/03-ui-components/05-side-menu-drag-handle-items/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/05-side-menu-drag-handle-items/vite.config.ts +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/tsconfig.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/tsconfig.json +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/vite.config.ts b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/vite.config.ts +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/tsconfig.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/tsconfig.json +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/vite.config.ts b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/vite.config.ts +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/tsconfig.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/tsconfig.json +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/vite.config.ts b/examples/03-ui-components/10-suggestion-menus-grid-mentions/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/vite.config.ts +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/11-uppy-file-panel/tsconfig.json b/examples/03-ui-components/11-uppy-file-panel/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/11-uppy-file-panel/tsconfig.json +++ b/examples/03-ui-components/11-uppy-file-panel/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/11-uppy-file-panel/vite.config.ts b/examples/03-ui-components/11-uppy-file-panel/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/11-uppy-file-panel/vite.config.ts +++ b/examples/03-ui-components/11-uppy-file-panel/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/12-static-formatting-toolbar/tsconfig.json b/examples/03-ui-components/12-static-formatting-toolbar/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/12-static-formatting-toolbar/tsconfig.json +++ b/examples/03-ui-components/12-static-formatting-toolbar/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/12-static-formatting-toolbar/vite.config.ts b/examples/03-ui-components/12-static-formatting-toolbar/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/12-static-formatting-toolbar/vite.config.ts +++ b/examples/03-ui-components/12-static-formatting-toolbar/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/13-custom-ui/tsconfig.json b/examples/03-ui-components/13-custom-ui/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/13-custom-ui/tsconfig.json +++ b/examples/03-ui-components/13-custom-ui/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/13-custom-ui/vite.config.ts b/examples/03-ui-components/13-custom-ui/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/13-custom-ui/vite.config.ts +++ b/examples/03-ui-components/13-custom-ui/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/15-advanced-tables/tsconfig.json b/examples/03-ui-components/15-advanced-tables/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/15-advanced-tables/tsconfig.json +++ b/examples/03-ui-components/15-advanced-tables/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/15-advanced-tables/vite.config.ts b/examples/03-ui-components/15-advanced-tables/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/15-advanced-tables/vite.config.ts +++ b/examples/03-ui-components/15-advanced-tables/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/16-link-toolbar-buttons/tsconfig.json b/examples/03-ui-components/16-link-toolbar-buttons/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/16-link-toolbar-buttons/tsconfig.json +++ b/examples/03-ui-components/16-link-toolbar-buttons/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/16-link-toolbar-buttons/vite.config.ts b/examples/03-ui-components/16-link-toolbar-buttons/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/16-link-toolbar-buttons/vite.config.ts +++ b/examples/03-ui-components/16-link-toolbar-buttons/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/17-advanced-tables-2/tsconfig.json b/examples/03-ui-components/17-advanced-tables-2/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/17-advanced-tables-2/tsconfig.json +++ b/examples/03-ui-components/17-advanced-tables-2/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/17-advanced-tables-2/vite.config.ts b/examples/03-ui-components/17-advanced-tables-2/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/17-advanced-tables-2/vite.config.ts +++ b/examples/03-ui-components/17-advanced-tables-2/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/18-drag-n-drop/tsconfig.json b/examples/03-ui-components/18-drag-n-drop/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/18-drag-n-drop/tsconfig.json +++ b/examples/03-ui-components/18-drag-n-drop/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/18-drag-n-drop/vite.config.ts b/examples/03-ui-components/18-drag-n-drop/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/18-drag-n-drop/vite.config.ts +++ b/examples/03-ui-components/18-drag-n-drop/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/03-ui-components/20-portal-elements/tsconfig.json b/examples/03-ui-components/20-portal-elements/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/03-ui-components/20-portal-elements/tsconfig.json +++ b/examples/03-ui-components/20-portal-elements/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/03-ui-components/20-portal-elements/vite.config.ts b/examples/03-ui-components/20-portal-elements/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/03-ui-components/20-portal-elements/vite.config.ts +++ b/examples/03-ui-components/20-portal-elements/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/04-theming/01-theming-dom-attributes/tsconfig.json b/examples/04-theming/01-theming-dom-attributes/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/04-theming/01-theming-dom-attributes/tsconfig.json +++ b/examples/04-theming/01-theming-dom-attributes/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/04-theming/01-theming-dom-attributes/vite.config.ts b/examples/04-theming/01-theming-dom-attributes/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/04-theming/01-theming-dom-attributes/vite.config.ts +++ b/examples/04-theming/01-theming-dom-attributes/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/04-theming/02-changing-font/tsconfig.json b/examples/04-theming/02-changing-font/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/04-theming/02-changing-font/tsconfig.json +++ b/examples/04-theming/02-changing-font/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/04-theming/02-changing-font/vite.config.ts b/examples/04-theming/02-changing-font/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/04-theming/02-changing-font/vite.config.ts +++ b/examples/04-theming/02-changing-font/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/04-theming/03-theming-css/tsconfig.json b/examples/04-theming/03-theming-css/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/04-theming/03-theming-css/tsconfig.json +++ b/examples/04-theming/03-theming-css/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/04-theming/03-theming-css/vite.config.ts b/examples/04-theming/03-theming-css/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/04-theming/03-theming-css/vite.config.ts +++ b/examples/04-theming/03-theming-css/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/04-theming/04-theming-css-variables/tsconfig.json b/examples/04-theming/04-theming-css-variables/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/04-theming/04-theming-css-variables/tsconfig.json +++ b/examples/04-theming/04-theming-css-variables/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/04-theming/04-theming-css-variables/vite.config.ts b/examples/04-theming/04-theming-css-variables/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/04-theming/04-theming-css-variables/vite.config.ts +++ b/examples/04-theming/04-theming-css-variables/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/04-theming/05-theming-css-variables-code/tsconfig.json b/examples/04-theming/05-theming-css-variables-code/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/04-theming/05-theming-css-variables-code/tsconfig.json +++ b/examples/04-theming/05-theming-css-variables-code/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/04-theming/05-theming-css-variables-code/vite.config.ts b/examples/04-theming/05-theming-css-variables-code/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/04-theming/05-theming-css-variables-code/vite.config.ts +++ b/examples/04-theming/05-theming-css-variables-code/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/04-theming/06-code-block/src/App.tsx b/examples/04-theming/06-code-block/src/App.tsx index 82d10bae9e..b5b3fbbd3a 100644 --- a/examples/04-theming/06-code-block/src/App.tsx +++ b/examples/04-theming/06-code-block/src/App.tsx @@ -3,12 +3,16 @@ import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; import { useCreateBlockNote } from "@blocknote/react"; -// This packages some of the most used languages in on-demand bundle -import { codeBlockOptions } from "@blocknote/code-block"; +// This packages some of the most used languages in on-demand bundle, and a +// ready-to-use syntax highlighter extension configured with them. +import { codeBlockOptions, syntaxHighlighter } from "@blocknote/code-block"; export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ + // Adding the syntax highlighter extension enables syntax highlighting for + // the code block. Without it, code renders as plain text. + extensions: [syntaxHighlighter], schema: BlockNoteSchema.create().extend({ blockSpecs: { codeBlock: createCodeBlockSpec(codeBlockOptions), diff --git a/examples/04-theming/06-code-block/tsconfig.json b/examples/04-theming/06-code-block/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/04-theming/06-code-block/tsconfig.json +++ b/examples/04-theming/06-code-block/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/04-theming/06-code-block/vite.config.ts b/examples/04-theming/06-code-block/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/04-theming/06-code-block/vite.config.ts +++ b/examples/04-theming/06-code-block/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/04-theming/07-custom-code-block/.bnexample.json b/examples/04-theming/07-custom-code-block/.bnexample.json index 84166710e3..0ab79ff45e 100644 --- a/examples/04-theming/07-custom-code-block/.bnexample.json +++ b/examples/04-theming/07-custom-code-block/.bnexample.json @@ -5,10 +5,10 @@ "tags": ["Basic"], "dependencies": { "@blocknote/code-block": "latest", - "@shikijs/core": "^4", - "@shikijs/engine-javascript": "^4", - "@shikijs/langs-precompiled": "^4", - "@shikijs/themes": "^4", - "@shikijs/types": "^4" + "@shikijs/core": "^4.4.3", + "@shikijs/engine-javascript": "^4.4.3", + "@shikijs/langs-precompiled": "^4.4.3", + "@shikijs/themes": "^4.4.3", + "@shikijs/types": "^4.4.3" } } diff --git a/examples/04-theming/07-custom-code-block/package.json b/examples/04-theming/07-custom-code-block/package.json index 6c70f6b12b..07a7589951 100644 --- a/examples/04-theming/07-custom-code-block/package.json +++ b/examples/04-theming/07-custom-code-block/package.json @@ -21,11 +21,11 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/code-block": "latest", - "@shikijs/core": "^4", - "@shikijs/engine-javascript": "^4", - "@shikijs/langs-precompiled": "^4", - "@shikijs/themes": "^4", - "@shikijs/types": "^4" + "@shikijs/core": "^4.4.3", + "@shikijs/engine-javascript": "^4.4.3", + "@shikijs/langs-precompiled": "^4.4.3", + "@shikijs/themes": "^4.4.3", + "@shikijs/types": "^4.4.3" }, "devDependencies": { "@types/react": "^19.2.3", diff --git a/examples/04-theming/07-custom-code-block/src/App.tsx b/examples/04-theming/07-custom-code-block/src/App.tsx index 8a9c74eac1..387d3463c8 100644 --- a/examples/04-theming/07-custom-code-block/src/App.tsx +++ b/examples/04-theming/07-custom-code-block/src/App.tsx @@ -1,4 +1,8 @@ -import { BlockNoteSchema, createCodeBlockSpec } from "@blocknote/core"; +import { + BlockNoteSchema, + createCodeBlockSpec, + SyntaxHighlightingExtension, +} from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; @@ -6,9 +10,22 @@ import { useCreateBlockNote } from "@blocknote/react"; // Bundle created from `npx shiki-codegen --langs typescript,javascript,react --themes light-plus,dark-plus --engine javascript --precompiled ./shiki.bundle.ts` import { createHighlighter } from "./shiki.bundle"; +// Syntax highlighting is a separate extension, configured with a highlighter. +// Here we build one from our own custom Shiki bundle (with `dark-plus` / +// `light-plus` themes) and pass it to the editor's `extensions` below. +const syntaxHighlighter = SyntaxHighlightingExtension({ + // This creates a highlighter, it can be asynchronous to load it afterwards + createHighlighter: () => + createHighlighter({ + themes: ["dark-plus", "light-plus"], + langs: [], + }), +}); + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ + extensions: [syntaxHighlighter], schema: BlockNoteSchema.create().extend({ blockSpecs: { codeBlock: createCodeBlockSpec({ @@ -27,12 +44,6 @@ export default function App() { name: "Vue", }, }, - // This creates a highlighter, it can be asynchronous to load it afterwards - createHighlighter: () => - createHighlighter({ - themes: ["dark-plus", "light-plus"], - langs: [], - }), }), }, }), diff --git a/examples/04-theming/07-custom-code-block/tsconfig.json b/examples/04-theming/07-custom-code-block/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/04-theming/07-custom-code-block/tsconfig.json +++ b/examples/04-theming/07-custom-code-block/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/04-theming/07-custom-code-block/vite.config.ts b/examples/04-theming/07-custom-code-block/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/04-theming/07-custom-code-block/vite.config.ts +++ b/examples/04-theming/07-custom-code-block/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/01-converting-blocks-to-html/tsconfig.json b/examples/05-interoperability/01-converting-blocks-to-html/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/tsconfig.json +++ b/examples/05-interoperability/01-converting-blocks-to-html/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/01-converting-blocks-to-html/vite.config.ts b/examples/05-interoperability/01-converting-blocks-to-html/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/vite.config.ts +++ b/examples/05-interoperability/01-converting-blocks-to-html/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/02-converting-blocks-from-html/tsconfig.json b/examples/05-interoperability/02-converting-blocks-from-html/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/tsconfig.json +++ b/examples/05-interoperability/02-converting-blocks-from-html/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/02-converting-blocks-from-html/vite.config.ts b/examples/05-interoperability/02-converting-blocks-from-html/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/vite.config.ts +++ b/examples/05-interoperability/02-converting-blocks-from-html/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/03-converting-blocks-to-md/tsconfig.json b/examples/05-interoperability/03-converting-blocks-to-md/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/tsconfig.json +++ b/examples/05-interoperability/03-converting-blocks-to-md/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/03-converting-blocks-to-md/vite.config.ts b/examples/05-interoperability/03-converting-blocks-to-md/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/vite.config.ts +++ b/examples/05-interoperability/03-converting-blocks-to-md/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/04-converting-blocks-from-md/tsconfig.json b/examples/05-interoperability/04-converting-blocks-from-md/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/tsconfig.json +++ b/examples/05-interoperability/04-converting-blocks-from-md/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/04-converting-blocks-from-md/vite.config.ts b/examples/05-interoperability/04-converting-blocks-from-md/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/vite.config.ts +++ b/examples/05-interoperability/04-converting-blocks-from-md/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json b/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json index 6115e659a5..553ec97d2c 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json @@ -4,9 +4,13 @@ "author": "yousefed", "tags": ["Interoperability"], "dependencies": { - "@blocknote/xl-pdf-exporter": "latest", + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-multi-column": "latest", - "@react-pdf/renderer": "^4.3.0" + "@blocknote/xl-pdf-exporter": "latest", + "@react-pdf/math": "^2.0.1", + "@react-pdf/renderer": "^4.5.1", + "mathjax-full": "^3.2.2" }, "pro": true } diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json index 0f196ae378..30c28d0430 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json @@ -20,9 +20,13 @@ "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", - "@blocknote/xl-pdf-exporter": "latest", + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-multi-column": "latest", - "@react-pdf/renderer": "^4.3.0" + "@blocknote/xl-pdf-exporter": "latest", + "@react-pdf/math": "^2.0.1", + "@react-pdf/renderer": "^4.5.1", + "mathjax-full": "^3.2.2" }, "devDependencies": { "@types/react": "^19.2.3", diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx b/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx index 4aaad9c805..956e045e8f 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx @@ -8,6 +8,11 @@ import "@blocknote/core/fonts/inter.css"; import * as locales from "@blocknote/core/locales"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { createReactDiagramBlockSpec } from "@blocknote/diagram-block"; +import { + createReactInlineMathSpec, + createReactMathBlockSpec, +} from "@blocknote/math-block"; import { SuggestionMenuController, getDefaultReactSlashMenuItems, @@ -24,6 +29,11 @@ import { PDFExporter, pdfDefaultSchemaMappings, } from "@blocknote/xl-pdf-exporter"; +import { diagramBlockMapping } from "@blocknote/diagram-block/pdf-exporter"; +import { + inlineMathMapping, + mathBlockMapping, +} from "@blocknote/math-block/pdf-exporter"; import { pdf, PDFViewer } from "@react-pdf/renderer"; import { JSX, useEffect, useMemo, useReducer, useState } from "react"; @@ -38,7 +48,16 @@ export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ // Adds support for page breaks & multi-column blocks. - schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())), + // Adds support for math & diagram blocks. + schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())).extend({ + blockSpecs: { + mathBlock: createReactMathBlockSpec(), + diagram: createReactDiagramBlockSpec(), + }, + inlineContentSpecs: { + math: createReactInlineMathSpec(), + }, + }), dropCursor: multiColumnDropCursor, dictionary: { ...locales.en, @@ -330,6 +349,31 @@ export default function App() { console.log("Hello World", message); };`, }, + { + type: "mathBlock", + content: "a^2 = \\sqrt{b^2 + c^2}", + }, + { + type: "diagram", + content: `graph TD + A[Start] --> B{Works?} + B -->|Yes| C[Ship it] + B -->|No| A`, + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline math: ", + styles: {}, + }, + { + type: "math", + content: "e^{i\\pi} + 1 = 0", + }, + ], + }, { type: "columnList", children: [ @@ -402,7 +446,21 @@ export default function App() { // Exports the editor document to PDF whenever it changes. const onChange = async () => { - const exporter = new PDFExporter(editor.schema, pdfDefaultSchemaMappings); + const exporter = new PDFExporter(editor.schema, { + ...pdfDefaultSchemaMappings, + blockMapping: { + ...pdfDefaultSchemaMappings.blockMapping, + // Embeds diagrams as images instead of their Mermaid source. + diagram: diagramBlockMapping, + // Renders math blocks as formulas instead of their LaTeX source. + mathBlock: mathBlockMapping, + }, + inlineContentMapping: { + ...pdfDefaultSchemaMappings.inlineContentMapping, + // Renders inline math as formula images instead of its LaTeX source. + math: inlineMathMapping, + }, + }); const pdfDocument = await exporter.toReactPDFDocument(editor.document); setPDFDocument(pdfDocument); forceRerender(); diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/tsconfig.json b/examples/05-interoperability/05-converting-blocks-to-pdf/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/tsconfig.json +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/vite.config.ts b/examples/05-interoperability/05-converting-blocks-to-pdf/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/vite.config.ts +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/.bnexample.json b/examples/05-interoperability/06-converting-blocks-to-docx/.bnexample.json index 17ec620bc9..f370c604f4 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/.bnexample.json +++ b/examples/05-interoperability/06-converting-blocks-to-docx/.bnexample.json @@ -4,8 +4,11 @@ "author": "yousefed", "tags": [""], "dependencies": { + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-docx-exporter": "latest", - "@blocknote/xl-multi-column": "latest" + "@blocknote/xl-multi-column": "latest", + "katex": "^0.16.11" }, "pro": true } diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/package.json b/examples/05-interoperability/06-converting-blocks-to-docx/package.json index 28ab63e55d..2a70496c6e 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/package.json +++ b/examples/05-interoperability/06-converting-blocks-to-docx/package.json @@ -20,8 +20,11 @@ "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-docx-exporter": "latest", - "@blocknote/xl-multi-column": "latest" + "@blocknote/xl-multi-column": "latest", + "katex": "^0.16.11" }, "devDependencies": { "@types/react": "^19.2.3", diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx b/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx index 4b873d9a40..8907bf760f 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx +++ b/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx @@ -8,6 +8,11 @@ import "@blocknote/core/fonts/inter.css"; import * as locales from "@blocknote/core/locales"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { + createReactInlineMathSpec, + createReactMathBlockSpec, +} from "@blocknote/math-block"; +import { createReactDiagramBlockSpec } from "@blocknote/diagram-block"; import { SuggestionMenuController, getDefaultReactSlashMenuItems, @@ -18,6 +23,11 @@ import { DOCXExporter, docxDefaultSchemaMappings, } from "@blocknote/xl-docx-exporter"; +import { diagramBlockMapping } from "@blocknote/diagram-block/docx-exporter"; +import { + inlineMathMapping, + mathBlockMapping, +} from "@blocknote/math-block/docx-exporter"; import { getMultiColumnSlashMenuItems, multiColumnDropCursor, @@ -32,7 +42,16 @@ export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ // Adds support for page breaks & multi-column blocks. - schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())), + // Adds support for math & diagram blocks. + schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())).extend({ + blockSpecs: { + mathBlock: createReactMathBlockSpec(), + diagram: createReactDiagramBlockSpec(), + }, + inlineContentSpecs: { + math: createReactInlineMathSpec(), + }, + }), dropCursor: multiColumnDropCursor, dictionary: { ...locales.en, @@ -324,6 +343,31 @@ export default function App() { console.log("Hello World", message); };`, }, + { + type: "mathBlock", + content: "a^2 = \\sqrt{b^2 + c^2}", + }, + { + type: "diagram", + content: `graph TD + A[Start] --> B{Works?} + B -->|Yes| C[Ship it] + B -->|No| A`, + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline math: ", + styles: {}, + }, + { + type: "math", + content: "e^{i\\pi} + 1 = 0", + }, + ], + }, { type: "columnList", @@ -396,7 +440,23 @@ export default function App() { // Exports the editor content to DOCX and downloads it. const onDownloadClick = async () => { - const exporter = new DOCXExporter(editor.schema, docxDefaultSchemaMappings); + const exporter = new DOCXExporter(editor.schema, { + ...docxDefaultSchemaMappings, + blockMapping: { + ...docxDefaultSchemaMappings.blockMapping, + // Embeds diagrams as images instead of their Mermaid source. + diagram: diagramBlockMapping, + // Renders math blocks as native equations instead of their LaTeX + // source. + mathBlock: mathBlockMapping, + }, + inlineContentMapping: { + ...docxDefaultSchemaMappings.inlineContentMapping, + // Renders inline math as native equations instead of its LaTeX + // source. + math: inlineMathMapping, + }, + }); const blob = await exporter.toBlob(editor.document); const link = document.createElement("a"); diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/tsconfig.json b/examples/05-interoperability/06-converting-blocks-to-docx/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/tsconfig.json +++ b/examples/05-interoperability/06-converting-blocks-to-docx/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/vite.config.ts b/examples/05-interoperability/06-converting-blocks-to-docx/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/vite.config.ts +++ b/examples/05-interoperability/06-converting-blocks-to-docx/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/.bnexample.json b/examples/05-interoperability/07-converting-blocks-to-odt/.bnexample.json index 7e3174aeea..3fee215859 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/.bnexample.json +++ b/examples/05-interoperability/07-converting-blocks-to-odt/.bnexample.json @@ -4,8 +4,11 @@ "author": "areknawo", "tags": [""], "dependencies": { + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", + "@blocknote/xl-multi-column": "latest", "@blocknote/xl-odt-exporter": "latest", - "@blocknote/xl-multi-column": "latest" + "katex": "^0.16.11" }, "pro": true } diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/package.json b/examples/05-interoperability/07-converting-blocks-to-odt/package.json index e615bcb5e9..2e814b5849 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/package.json +++ b/examples/05-interoperability/07-converting-blocks-to-odt/package.json @@ -20,8 +20,11 @@ "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", + "@blocknote/xl-multi-column": "latest", "@blocknote/xl-odt-exporter": "latest", - "@blocknote/xl-multi-column": "latest" + "katex": "^0.16.11" }, "devDependencies": { "@types/react": "^19.2.3", diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx b/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx index 7b884ac658..518617bafd 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx +++ b/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx @@ -8,6 +8,11 @@ import * as locales from "@blocknote/core/locales"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { + createReactInlineMathSpec, + createReactMathBlockSpec, +} from "@blocknote/math-block"; +import { createReactDiagramBlockSpec } from "@blocknote/diagram-block"; import { SuggestionMenuController, getDefaultReactSlashMenuItems, @@ -18,6 +23,11 @@ import { ODTExporter, odtDefaultSchemaMappings, } from "@blocknote/xl-odt-exporter"; +import { diagramBlockMapping } from "@blocknote/diagram-block/odt-exporter"; +import { + inlineMathMapping, + mathBlockMapping, +} from "@blocknote/math-block/odt-exporter"; import { getMultiColumnSlashMenuItems, multiColumnDropCursor, @@ -32,7 +42,16 @@ export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ // Adds support for page breaks & multi-column blocks. - schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())), + // Adds support for math & diagram blocks. + schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())).extend({ + blockSpecs: { + mathBlock: createReactMathBlockSpec(), + diagram: createReactDiagramBlockSpec(), + }, + inlineContentSpecs: { + math: createReactInlineMathSpec(), + }, + }), dropCursor: multiColumnDropCursor, dictionary: { ...locales.en, @@ -324,6 +343,31 @@ export default function App() { console.log("Hello World", message); };`, }, + { + type: "mathBlock", + content: "a^2 = \\sqrt{b^2 + c^2}", + }, + { + type: "diagram", + content: `graph TD + A[Start] --> B{Works?} + B -->|Yes| C[Ship it] + B -->|No| A`, + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline math: ", + styles: {}, + }, + { + type: "math", + content: "e^{i\\pi} + 1 = 0", + }, + ], + }, { type: "columnList", children: [ @@ -395,7 +439,23 @@ export default function App() { // Exports the editor content to ODT and downloads it. const onDownloadClick = async () => { - const exporter = new ODTExporter(editor.schema, odtDefaultSchemaMappings); + const exporter = new ODTExporter(editor.schema, { + ...odtDefaultSchemaMappings, + blockMapping: { + ...odtDefaultSchemaMappings.blockMapping, + // Embeds diagrams as images instead of their Mermaid source. + diagram: diagramBlockMapping, + // Renders math blocks as native equations instead of their LaTeX + // source. + mathBlock: mathBlockMapping, + }, + inlineContentMapping: { + ...odtDefaultSchemaMappings.inlineContentMapping, + // Renders inline math as native equations instead of its LaTeX + // source. + math: inlineMathMapping, + }, + }); const blob = await exporter.toODTDocument(editor.document); const link = document.createElement("a"); diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/tsconfig.json b/examples/05-interoperability/07-converting-blocks-to-odt/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/tsconfig.json +++ b/examples/05-interoperability/07-converting-blocks-to-odt/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/vite.config.ts b/examples/05-interoperability/07-converting-blocks-to-odt/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/vite.config.ts +++ b/examples/05-interoperability/07-converting-blocks-to-odt/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/.bnexample.json b/examples/05-interoperability/08-converting-blocks-to-react-email/.bnexample.json index 34a9fffa9c..12f951eec2 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/.bnexample.json +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/.bnexample.json @@ -4,6 +4,8 @@ "author": "jmarbutt", "tags": [""], "dependencies": { + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-email-exporter": "latest", "@react-email/render": "^2.0.4" }, diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json index 409f449bbd..c69fbbbcc0 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json @@ -20,6 +20,8 @@ "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-email-exporter": "latest", "@react-email/render": "^2.0.4" }, diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx b/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx index 9db579a8d1..86640c049b 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx @@ -17,6 +17,16 @@ import { useCreateBlockNote, usePrefersColorScheme, } from "@blocknote/react"; +import { createReactDiagramBlockSpec } from "@blocknote/diagram-block"; +import { createDiagramBlockMapping } from "@blocknote/diagram-block/email-exporter"; +import { + createReactInlineMathSpec, + createReactMathBlockSpec, +} from "@blocknote/math-block"; +import { + createInlineMathMapping, + createMathBlockMapping, +} from "@blocknote/math-block/email-exporter"; import { ReactEmailExporter, reactEmailDefaultSchemaMappings, @@ -33,7 +43,16 @@ export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ // Adds support for page breaks. - schema: withPageBreak(BlockNoteSchema.create()), + // Adds support for math & diagram blocks. + schema: withPageBreak(BlockNoteSchema.create()).extend({ + blockSpecs: { + mathBlock: createReactMathBlockSpec(), + diagram: createReactDiagramBlockSpec(), + }, + inlineContentSpecs: { + math: createReactInlineMathSpec(), + }, + }), // Adds support for advanced table features. tables: { splitCells: true, @@ -320,6 +339,31 @@ export default function App() { console.log("Hello World", message); };`, }, + { + type: "mathBlock", + content: "a^2 = \\sqrt{b^2 + c^2}", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline math: ", + styles: {}, + }, + { + type: "math", + content: "e^{i\\pi} + 1 = 0", + }, + ], + }, + { + type: "diagram", + content: `graph TD + A[Start] --> B{Works?} + B -->|Yes| C[Ship it] + B -->|No| A`, + }, ], }); @@ -345,7 +389,24 @@ export default function App() { existingContext?.colorSchemePreference || systemColorScheme; const exporter = new ReactEmailExporter( editor.schema, - reactEmailDefaultSchemaMappings, + { + ...reactEmailDefaultSchemaMappings, + blockMapping: { + ...reactEmailDefaultSchemaMappings.blockMapping, + // Renders math blocks & diagrams as images with the source as alt + // text. Embedded as data URLs by default - when actually sending + // emails, deliver them as inline attachments instead (which Gmail + // & Outlook also display) by passing an `imageDelivery` from + // `createCIDImageDelivery()` to each mapping, and handing its + // `attachments` to your mailer alongside the HTML. + mathBlock: createMathBlockMapping(), + diagram: createDiagramBlockMapping(), + }, + inlineContentMapping: { + ...reactEmailDefaultSchemaMappings.inlineContentMapping, + math: createInlineMathMapping(), + }, + }, { colors: colorScheme === "dark" ? COLORS_DARK_MODE_DEFAULT : COLORS_DEFAULT, diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/tsconfig.json b/examples/05-interoperability/08-converting-blocks-to-react-email/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/tsconfig.json +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/vite.config.ts b/examples/05-interoperability/08-converting-blocks-to-react-email/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/vite.config.ts +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/09-blocks-to-html-static-render/tsconfig.json b/examples/05-interoperability/09-blocks-to-html-static-render/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/09-blocks-to-html-static-render/tsconfig.json +++ b/examples/05-interoperability/09-blocks-to-html-static-render/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/09-blocks-to-html-static-render/vite.config.ts b/examples/05-interoperability/09-blocks-to-html-static-render/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/09-blocks-to-html-static-render/vite.config.ts +++ b/examples/05-interoperability/09-blocks-to-html-static-render/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/05-interoperability/10-static-html-render/tsconfig.json b/examples/05-interoperability/10-static-html-render/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/05-interoperability/10-static-html-render/tsconfig.json +++ b/examples/05-interoperability/10-static-html-render/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/05-interoperability/10-static-html-render/vite.config.ts b/examples/05-interoperability/10-static-html-render/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/05-interoperability/10-static-html-render/vite.config.ts +++ b/examples/05-interoperability/10-static-html-render/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/01-alert-block/tsconfig.json b/examples/06-custom-schema/01-alert-block/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/01-alert-block/tsconfig.json +++ b/examples/06-custom-schema/01-alert-block/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/01-alert-block/vite.config.ts b/examples/06-custom-schema/01-alert-block/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/01-alert-block/vite.config.ts +++ b/examples/06-custom-schema/01-alert-block/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/tsconfig.json b/examples/06-custom-schema/02-suggestion-menus-mentions/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/tsconfig.json +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/vite.config.ts b/examples/06-custom-schema/02-suggestion-menus-mentions/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/vite.config.ts +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/03-font-style/tsconfig.json b/examples/06-custom-schema/03-font-style/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/03-font-style/tsconfig.json +++ b/examples/06-custom-schema/03-font-style/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/03-font-style/vite.config.ts b/examples/06-custom-schema/03-font-style/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/03-font-style/vite.config.ts +++ b/examples/06-custom-schema/03-font-style/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/04-pdf-file-block/tsconfig.json b/examples/06-custom-schema/04-pdf-file-block/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/04-pdf-file-block/tsconfig.json +++ b/examples/06-custom-schema/04-pdf-file-block/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/04-pdf-file-block/vite.config.ts b/examples/06-custom-schema/04-pdf-file-block/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/04-pdf-file-block/vite.config.ts +++ b/examples/06-custom-schema/04-pdf-file-block/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/05-alert-block-full-ux/tsconfig.json b/examples/06-custom-schema/05-alert-block-full-ux/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/05-alert-block-full-ux/tsconfig.json +++ b/examples/06-custom-schema/05-alert-block-full-ux/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/05-alert-block-full-ux/vite.config.ts b/examples/06-custom-schema/05-alert-block-full-ux/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/05-alert-block-full-ux/vite.config.ts +++ b/examples/06-custom-schema/05-alert-block-full-ux/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json b/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json +++ b/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/06-toggleable-blocks/vite.config.ts b/examples/06-custom-schema/06-toggleable-blocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/vite.config.ts +++ b/examples/06-custom-schema/06-toggleable-blocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/07-configuring-blocks/tsconfig.json b/examples/06-custom-schema/07-configuring-blocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/07-configuring-blocks/tsconfig.json +++ b/examples/06-custom-schema/07-configuring-blocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/07-configuring-blocks/vite.config.ts b/examples/06-custom-schema/07-configuring-blocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/07-configuring-blocks/vite.config.ts +++ b/examples/06-custom-schema/07-configuring-blocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/08-non-editable-block/tsconfig.json b/examples/06-custom-schema/08-non-editable-block/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/08-non-editable-block/tsconfig.json +++ b/examples/06-custom-schema/08-non-editable-block/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/08-non-editable-block/vite.config.ts b/examples/06-custom-schema/08-non-editable-block/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/08-non-editable-block/vite.config.ts +++ b/examples/06-custom-schema/08-non-editable-block/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/09-math-block/.bnexample.json b/examples/06-custom-schema/09-math-block/.bnexample.json new file mode 100644 index 0000000000..d7b46b399c --- /dev/null +++ b/examples/06-custom-schema/09-math-block/.bnexample.json @@ -0,0 +1,17 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Blocks", + "Custom Schemas", + "Suggestion Menus", + "Slash Menu" + ], + "dependencies": { + "@blocknote/code-block": "latest", + "@blocknote/math-block": "latest", + "react-icons": "^5.5.0" + } +} diff --git a/examples/06-custom-schema/09-math-block/README.md b/examples/06-custom-schema/09-math-block/README.md new file mode 100644 index 0000000000..9f2b15c570 --- /dev/null +++ b/examples/06-custom-schema/09-math-block/README.md @@ -0,0 +1,10 @@ +# Math Block + +In this example, we register the `@blocknote/math-block` block in a custom schema. The math block renders LaTeX as MathML (using Temml) for the browser to display natively, and reveals an editable LaTeX source popup when selected. Exporting to HTML produces a MathML `` element, and pasting MathML back in is converted to LaTeX. + +**Try it out:** Click a formula to edit its LaTeX! + +**Relevant Docs:** + +- [Custom Blocks](/docs/features/custom-schemas/custom-blocks) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/06-custom-schema/09-math-block/index.html b/examples/06-custom-schema/09-math-block/index.html new file mode 100644 index 0000000000..034154dbcf --- /dev/null +++ b/examples/06-custom-schema/09-math-block/index.html @@ -0,0 +1,14 @@ + + + + + Math Block + + + +
+ + + diff --git a/examples/06-custom-schema/09-math-block/main.tsx b/examples/06-custom-schema/09-math-block/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/06-custom-schema/09-math-block/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/06-custom-schema/09-math-block/package.json b/examples/06-custom-schema/09-math-block/package.json new file mode 100644 index 0000000000..e1390e5959 --- /dev/null +++ b/examples/06-custom-schema/09-math-block/package.json @@ -0,0 +1,33 @@ +{ + "name": "@blocknote/example-custom-schema-math-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "@blocknote/code-block": "latest", + "@blocknote/math-block": "latest", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.0" + } +} diff --git a/examples/06-custom-schema/09-math-block/src/App.tsx b/examples/06-custom-schema/09-math-block/src/App.tsx new file mode 100644 index 0000000000..554b3402bb --- /dev/null +++ b/examples/06-custom-schema/09-math-block/src/App.tsx @@ -0,0 +1,110 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteSchema, combineByGroup } from "@blocknote/core"; +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import * as locales from "@blocknote/core/locales"; +import { syntaxHighlighter } from "@blocknote/code-block"; +import { + createReactInlineMathSpec, + createReactMathBlockSpec, + getMathBlockTypeSelectItems, + getMathSlashMenuItems, + locales as mathLocales, +} from "@blocknote/math-block"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + blockTypeSelectItems, + FormattingToolbar, + FormattingToolbarController, + getDefaultReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; + +// Our schema with block specs, which contain the configs and implementations for blocks +// that we want our editor to use. +const schema = BlockNoteSchema.create().extend({ + blockSpecs: { + // Creates an instance of the Math block and adds it to the schema. + mathBlock: createReactMathBlockSpec(), + }, + inlineContentSpecs: { + // Creates an instance of the inline Math content and adds it to the schema. + math: createReactInlineMathSpec(), + }, +}); + +export default function App() { + const editor = useCreateBlockNote({ + // The syntax highlighter extension highlights the LaTeX source of math + // blocks (they declare `highlight: () => "latex"`). Without it, they render + // as plain text. + extensions: [syntaxHighlighter], + schema, + // Merges the default dictionary with the math dictionary, under the `math` + // key the math block/inline content read their strings from. + dictionary: { + ...locales.en, + math: mathLocales.en, + }, + initialContent: [ + { + type: "paragraph", + content: "Click a formula to edit its LaTeX source:", + }, + { + type: "mathBlock", + content: "a^2 = \\sqrt{b^2 + c^2}", + }, + { + type: "mathBlock", + content: "\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}", + }, + { + type: "paragraph", + content: [ + "Equations can also be inline, like ", + { type: "math", content: "e^{i\\pi} + 1 = 0" }, + ". Click one to edit its LaTeX source.", + ], + }, + { + type: "paragraph", + content: "Press the '/' key to open the Slash Menu and add another", + }, + ], + }); + + // Renders the editor instance using a React component. + return ( + + {/* Replaces the default Formatting Toolbar, adding the Math block to the + block type select so blocks can be converted to it. */} + ( + + )} + /> + {/* Replaces the default Slash Menu. */} + { + // Gets the default slash menu items and adds the Math items at the + // end of their group ("Advanced"). + const items = combineByGroup( + getDefaultReactSlashMenuItems(editor), + getMathSlashMenuItems(editor), + ); + + // Returns filtered items based on the query. + return filterSuggestionItems(items, query); + }} + /> + + ); +} diff --git a/examples/06-custom-schema/09-math-block/tsconfig.json b/examples/06-custom-schema/09-math-block/tsconfig.json new file mode 100644 index 0000000000..2aa62c56e6 --- /dev/null +++ b/examples/06-custom-schema/09-math-block/tsconfig.json @@ -0,0 +1,32 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/06-custom-schema/09-math-block/vite-env.d.ts b/examples/06-custom-schema/09-math-block/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/06-custom-schema/09-math-block/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/06-custom-schema/09-math-block/vite.config.ts b/examples/06-custom-schema/09-math-block/vite.config.ts new file mode 100644 index 0000000000..a96f1f04ff --- /dev/null +++ b/examples/06-custom-schema/09-math-block/vite.config.ts @@ -0,0 +1,35 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/06-custom-schema/10-diagram-block/.bnexample.json b/examples/06-custom-schema/10-diagram-block/.bnexample.json new file mode 100644 index 0000000000..915c0c3db7 --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/.bnexample.json @@ -0,0 +1,17 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Intermediate", + "Blocks", + "Custom Schemas", + "Suggestion Menus", + "Slash Menu" + ], + "dependencies": { + "@blocknote/code-block": "latest", + "@blocknote/diagram-block": "latest", + "react-icons": "^5.5.0" + } +} diff --git a/examples/06-custom-schema/10-diagram-block/README.md b/examples/06-custom-schema/10-diagram-block/README.md new file mode 100644 index 0000000000..5e8ad1523b --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/README.md @@ -0,0 +1,10 @@ +# Diagram Block + +In this example, we register the `@blocknote/diagram-block` block in a custom schema. The block renders diagrams from [Mermaid](https://mermaid.js.org/) source code, showing the rendered diagram in place of the source and revealing an editable source popup when selected - built from the same `SourceBlockWithPreview` component the math block uses, so the block itself is only a few dozen lines. + +**Try it out:** Click a diagram to edit its Mermaid source! + +**Relevant Docs:** + +- [Custom Blocks](/docs/features/custom-schemas/custom-blocks) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/06-custom-schema/10-diagram-block/index.html b/examples/06-custom-schema/10-diagram-block/index.html new file mode 100644 index 0000000000..2a2ca2d29b --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/index.html @@ -0,0 +1,14 @@ + + + + + Diagram Block + + + +
+ + + diff --git a/examples/06-custom-schema/10-diagram-block/main.tsx b/examples/06-custom-schema/10-diagram-block/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/06-custom-schema/10-diagram-block/package.json b/examples/06-custom-schema/10-diagram-block/package.json new file mode 100644 index 0000000000..1e52cdd52c --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/package.json @@ -0,0 +1,33 @@ +{ + "name": "@blocknote/example-custom-schema-diagram-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "@blocknote/code-block": "latest", + "@blocknote/diagram-block": "latest", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.0" + } +} diff --git a/examples/06-custom-schema/10-diagram-block/src/App.tsx b/examples/06-custom-schema/10-diagram-block/src/App.tsx new file mode 100644 index 0000000000..c96af84261 --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/src/App.tsx @@ -0,0 +1,96 @@ +import { syntaxHighlighter } from "@blocknote/code-block"; +import { BlockNoteSchema, combineByGroup } from "@blocknote/core"; +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import * as locales from "@blocknote/core/locales"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + createReactDiagramBlockSpec, + getDiagramBlockTypeSelectItems, + getDiagramSlashMenuItems, + locales as diagramLocales, +} from "@blocknote/diagram-block"; +import { + blockTypeSelectItems, + FormattingToolbar, + FormattingToolbarController, + getDefaultReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; + +// Our schema with block specs, which contain the configs and implementations +// for blocks that we want our editor to use. +const schema = BlockNoteSchema.create().extend({ + blockSpecs: { + // Creates an instance of the Diagram block and adds it to the schema. + diagram: createReactDiagramBlockSpec(), + }, +}); + +export default function App() { + const editor = useCreateBlockNote({ + // The syntax highlighter extension highlights the Diagram block's Mermaid + // source in its popup (the block declares `highlight: () => "mermaid"`). + extensions: [syntaxHighlighter], + schema, + // Merges the default dictionary with the diagram dictionary, under the + // `diagram` key the diagram block reads its strings from. + dictionary: { + ...locales.en, + diagram: diagramLocales.en, + }, + initialContent: [ + { + type: "paragraph", + content: "Click a diagram to edit its Mermaid source:", + }, + { + type: "diagram", + content: `graph TD + A[Write docs] --> B{Diagram needed?} + B -->|Yes| C[Type /diagram] + B -->|No| D[Keep writing] + C --> D`, + }, + { + type: "paragraph", + content: "Press the '/' key to open the Slash Menu and add another", + }, + ], + }); + + // Renders the editor instance using a React component. + return ( + + {/* Replaces the default Formatting Toolbar, adding the Diagram block to + the block type select so blocks can be converted to it. */} + ( + + )} + /> + {/* Replaces the default Slash Menu. */} + { + // Gets the default slash menu items and adds the Diagram item at + // the end of its group ("Advanced"). + const items = combineByGroup( + getDefaultReactSlashMenuItems(editor), + getDiagramSlashMenuItems(editor), + ); + + // Returns filtered items based on the query. + return filterSuggestionItems(items, query); + }} + /> + + ); +} diff --git a/examples/06-custom-schema/10-diagram-block/tsconfig.json b/examples/06-custom-schema/10-diagram-block/tsconfig.json new file mode 100644 index 0000000000..2aa62c56e6 --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/tsconfig.json @@ -0,0 +1,32 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/06-custom-schema/10-diagram-block/vite-env.d.ts b/examples/06-custom-schema/10-diagram-block/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/06-custom-schema/10-diagram-block/vite.config.ts b/examples/06-custom-schema/10-diagram-block/vite.config.ts new file mode 100644 index 0000000000..a96f1f04ff --- /dev/null +++ b/examples/06-custom-schema/10-diagram-block/vite.config.ts @@ -0,0 +1,35 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/06-custom-schema/11-source-with-preview/.bnexample.json b/examples/06-custom-schema/11-source-with-preview/.bnexample.json new file mode 100644 index 0000000000..5539a634d2 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": ["Advanced", "Blocks", "Custom Schemas"], + "dependencies": { + "react-icons": "^5.5.0" + } +} diff --git a/examples/06-custom-schema/11-source-with-preview/README.md b/examples/06-custom-schema/11-source-with-preview/README.md new file mode 100644 index 0000000000..844fc92044 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/README.md @@ -0,0 +1,11 @@ +# Source with Preview Blocks + +In this example, we build custom blocks on the source-with-preview pattern — the same building blocks behind BlockNote's math and diagram blocks. A custom "CSV table" block renders its comma-separated source as a table, and a custom "color" inline content renders a CSS color as a swatch. Both show the rendered preview in place, while the source is edited in a popup. + +**Try it out:** Click the table or a color chip to edit its source! + +**Relevant Docs:** + +- [Source with Preview Blocks](/docs/features/custom-schemas/source-with-preview) +- [Custom Blocks](/docs/features/custom-schemas/custom-blocks) +- [Custom Inline Content](/docs/features/custom-schemas/custom-inline-content) diff --git a/examples/06-custom-schema/11-source-with-preview/index.html b/examples/06-custom-schema/11-source-with-preview/index.html new file mode 100644 index 0000000000..b577a83785 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/index.html @@ -0,0 +1,14 @@ + + + + + Source with Preview Blocks + + + +
+ + + diff --git a/examples/06-custom-schema/11-source-with-preview/main.tsx b/examples/06-custom-schema/11-source-with-preview/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/06-custom-schema/11-source-with-preview/package.json b/examples/06-custom-schema/11-source-with-preview/package.json new file mode 100644 index 0000000000..9d5da6eaf0 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-custom-schema-source-with-preview", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.0" + } +} diff --git a/examples/06-custom-schema/11-source-with-preview/src/App.tsx b/examples/06-custom-schema/11-source-with-preview/src/App.tsx new file mode 100644 index 0000000000..3485907844 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/src/App.tsx @@ -0,0 +1,214 @@ +import { + BlockNoteSchema, + createBlockConfig, + CustomInlineContentConfig, + plainContentToString, +} from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + createReactBlockSpec, + createReactInlineContentSpec, + PreviewPlaceholder, + ReactCustomBlockRenderProps, + ReactCustomInlineContentRenderProps, + SourceBlockWithPreview, + SourceInlineContentWithPreview, + useCreateBlockNote, +} from "@blocknote/react"; +import { TbTable } from "react-icons/tb"; + +import "./styles.css"; + +// A custom "CSV table" block: authored as comma-separated values in a source +// popup, rendered as a table. Built on the same source-with-preview pattern +// as BlockNote's math and diagram blocks. +const createCSVTableBlockConfig = createBlockConfig( + () => + ({ + type: "csvTable" as const, + propSchema: {}, + // The source is stored as the block's plain text content. + content: "plain" as const, + }) as const, +); + +type CSVTableBlockConfig = ReturnType; + +// Renders the CSV source to a table element, or reports invalid source as an +// error. Expected failures are values, not exceptions - the preview +// component decides how to show them. +function renderCSV( + source: string, +): { table: string[][]; error?: undefined } | { error: string } { + const rows = source + .split("\n") + .filter((row) => row.trim()) + .map((row) => row.split(",").map((cell) => cell.trim())); + if (rows.length === 0) { + return { error: "No rows" }; + } + if (rows.some((row) => row.length !== rows[0].length)) { + return { error: "All rows must have the same number of columns" }; + } + return { table: rows }; +} + +const CSVTablePreview = ( + props: ReactCustomBlockRenderProps, +) => { + // The block's content as plain text, i.e. the source to render. + const source = plainContentToString(props.block.content).trim(); + const result = renderCSV(source); + + return ( + + + {result.table.map((row, i) => ( + + {row.map((cell, j) => ( + {cell} + ))} + + ))} + + + ) : undefined + } + // Shown below the source in the popup while editing. + error={result.error} + // The compact error state shown in place of the preview. + errorPreview={ + } + text="Invalid CSV - click to fix" + /> + } + // Shown in place of the preview when the source is empty. + emptySourcePlaceholder={ + } text="Add a CSV table" /> + } + sourcePlaceholder="Enter comma-separated values" + /> + ); +}; + +const createCSVTableBlockSpec = createReactBlockSpec( + createCSVTableBlockConfig, + { + meta: { + code: true, + defining: true, + isolating: false, + // Marks the block as rendering a preview with an editable source popup + // (driven by an editor-wide extension - nothing to register). + hasPreview: true, + // Enter inserts a newline while the popup is open (multiline source); + // use "shift+enter" for single-line sources, where Enter closes the + // popup instead. + hardBreakShortcut: "enter", + }, + render: CSVTablePreview, + }, +); + +// A custom "color" inline content: authored as a CSS color, rendered as a +// color chip that flows with the text. +const colorChipConfig = { + type: "colorChip" as const, + propSchema: {}, + content: "plain" as const, +} satisfies CustomInlineContentConfig; + +const ColorChipPreview = ( + props: ReactCustomInlineContentRenderProps, +) => { + // For "plain" inline content, `content` is already a plain string. + const source = props.inlineContent.content.trim(); + const isValidColor = CSS.supports("color", source); + + return ( + + + {source} + + ) : undefined + } + error={isValidColor ? undefined : `Not a CSS color: "${source}"`} + sourcePlaceholder="Enter a CSS color" + /> + ); +}; + +const createColorChipSpec = () => + createReactInlineContentSpec(colorChipConfig, { + meta: { + code: true, + hasPreview: true, + }, + render: ColorChipPreview, + }); + +// Our schema with the two custom specs added. +const schema = BlockNoteSchema.create().extend({ + blockSpecs: { + csvTable: createCSVTableBlockSpec(), + }, + inlineContentSpecs: { + colorChip: createColorChipSpec(), + }, +}); + +export default function App() { + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "paragraph", + content: "Click the table to edit its comma-separated source:", + }, + { + type: "csvTable", + content: "Name, Role\nAda, Engineer\nGrace, Admiral", + }, + { + type: "paragraph", + content: [ + "Inline content works too - this color chip ", + { + type: "colorChip", + content: "rebeccapurple", + }, + " is editable when the selection is inside it.", + ], + }, + { + type: "paragraph", + }, + ], + }); + + return ; +} diff --git a/examples/06-custom-schema/11-source-with-preview/src/styles.css b/examples/06-custom-schema/11-source-with-preview/src/styles.css new file mode 100644 index 0000000000..167d4f7af2 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/src/styles.css @@ -0,0 +1,26 @@ +.csv-table { + border-collapse: collapse; +} + +.csv-table td { + border: 1px solid #ddd; + padding: 4px 12px; +} + +.color-chip { + display: inline-flex; + align-items: center; + gap: 0.3em; + border: 1px solid #ddd; + border-radius: 4px; + padding: 0 0.3em; + font-family: monospace; + font-size: 0.9em; +} + +.color-chip-swatch { + display: inline-block; + width: 0.8em; + height: 0.8em; + border-radius: 2px; +} diff --git a/examples/06-custom-schema/11-source-with-preview/tsconfig.json b/examples/06-custom-schema/11-source-with-preview/tsconfig.json new file mode 100644 index 0000000000..2aa62c56e6 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/tsconfig.json @@ -0,0 +1,32 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/06-custom-schema/11-source-with-preview/vite-env.d.ts b/examples/06-custom-schema/11-source-with-preview/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/06-custom-schema/11-source-with-preview/vite.config.ts b/examples/06-custom-schema/11-source-with-preview/vite.config.ts new file mode 100644 index 0000000000..a96f1f04ff --- /dev/null +++ b/examples/06-custom-schema/11-source-with-preview/vite.config.ts @@ -0,0 +1,35 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/06-custom-schema/draggable-inline-content/tsconfig.json b/examples/06-custom-schema/draggable-inline-content/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/draggable-inline-content/tsconfig.json +++ b/examples/06-custom-schema/draggable-inline-content/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/draggable-inline-content/vite.config.ts b/examples/06-custom-schema/draggable-inline-content/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/draggable-inline-content/vite.config.ts +++ b/examples/06-custom-schema/draggable-inline-content/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/react-custom-blocks/tsconfig.json b/examples/06-custom-schema/react-custom-blocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/react-custom-blocks/tsconfig.json +++ b/examples/06-custom-schema/react-custom-blocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/react-custom-blocks/vite.config.ts b/examples/06-custom-schema/react-custom-blocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/react-custom-blocks/vite.config.ts +++ b/examples/06-custom-schema/react-custom-blocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/react-custom-inline-content/tsconfig.json b/examples/06-custom-schema/react-custom-inline-content/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/react-custom-inline-content/tsconfig.json +++ b/examples/06-custom-schema/react-custom-inline-content/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/react-custom-inline-content/vite.config.ts b/examples/06-custom-schema/react-custom-inline-content/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/react-custom-inline-content/vite.config.ts +++ b/examples/06-custom-schema/react-custom-inline-content/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/06-custom-schema/react-custom-styles/tsconfig.json b/examples/06-custom-schema/react-custom-styles/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/06-custom-schema/react-custom-styles/tsconfig.json +++ b/examples/06-custom-schema/react-custom-styles/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/06-custom-schema/react-custom-styles/vite.config.ts b/examples/06-custom-schema/react-custom-styles/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/06-custom-schema/react-custom-styles/vite.config.ts +++ b/examples/06-custom-schema/react-custom-styles/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/01-partykit/tsconfig.json b/examples/07-collaboration/01-partykit/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/01-partykit/tsconfig.json +++ b/examples/07-collaboration/01-partykit/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/01-partykit/vite.config.ts b/examples/07-collaboration/01-partykit/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/01-partykit/vite.config.ts +++ b/examples/07-collaboration/01-partykit/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/02-liveblocks/tsconfig.json b/examples/07-collaboration/02-liveblocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/02-liveblocks/tsconfig.json +++ b/examples/07-collaboration/02-liveblocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/02-liveblocks/vite.config.ts b/examples/07-collaboration/02-liveblocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/02-liveblocks/vite.config.ts +++ b/examples/07-collaboration/02-liveblocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/03-y-sweet/tsconfig.json b/examples/07-collaboration/03-y-sweet/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/03-y-sweet/tsconfig.json +++ b/examples/07-collaboration/03-y-sweet/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/03-y-sweet/vite.config.ts b/examples/07-collaboration/03-y-sweet/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/03-y-sweet/vite.config.ts +++ b/examples/07-collaboration/03-y-sweet/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/04-electric-sql/tsconfig.json b/examples/07-collaboration/04-electric-sql/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/04-electric-sql/tsconfig.json +++ b/examples/07-collaboration/04-electric-sql/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/04-electric-sql/vite.config.ts b/examples/07-collaboration/04-electric-sql/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/04-electric-sql/vite.config.ts +++ b/examples/07-collaboration/04-electric-sql/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/05-comments/tsconfig.json b/examples/07-collaboration/05-comments/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/05-comments/tsconfig.json +++ b/examples/07-collaboration/05-comments/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/05-comments/vite.config.ts b/examples/07-collaboration/05-comments/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/05-comments/vite.config.ts +++ b/examples/07-collaboration/05-comments/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json b/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json +++ b/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/06-comments-with-sidebar/vite.config.ts b/examples/07-collaboration/06-comments-with-sidebar/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/vite.config.ts +++ b/examples/07-collaboration/06-comments-with-sidebar/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/07-ghost-writer/tsconfig.json b/examples/07-collaboration/07-ghost-writer/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/07-ghost-writer/tsconfig.json +++ b/examples/07-collaboration/07-ghost-writer/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/07-ghost-writer/vite.config.ts b/examples/07-collaboration/07-ghost-writer/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/07-ghost-writer/vite.config.ts +++ b/examples/07-collaboration/07-ghost-writer/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/08-forking/tsconfig.json b/examples/07-collaboration/08-forking/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/08-forking/tsconfig.json +++ b/examples/07-collaboration/08-forking/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/08-forking/vite.config.ts b/examples/07-collaboration/08-forking/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/08-forking/vite.config.ts +++ b/examples/07-collaboration/08-forking/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/09-comments-testing/tsconfig.json b/examples/07-collaboration/09-comments-testing/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/09-comments-testing/tsconfig.json +++ b/examples/07-collaboration/09-comments-testing/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/09-comments-testing/vite.config.ts b/examples/07-collaboration/09-comments-testing/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/09-comments-testing/vite.config.ts +++ b/examples/07-collaboration/09-comments-testing/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/10-suggestion-multi-editor/tsconfig.json b/examples/07-collaboration/10-suggestion-multi-editor/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/10-suggestion-multi-editor/tsconfig.json +++ b/examples/07-collaboration/10-suggestion-multi-editor/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/10-suggestion-multi-editor/vite.config.ts b/examples/07-collaboration/10-suggestion-multi-editor/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/10-suggestion-multi-editor/vite.config.ts +++ b/examples/07-collaboration/10-suggestion-multi-editor/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/11-versioning-yjs13/tsconfig.json b/examples/07-collaboration/11-versioning-yjs13/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/11-versioning-yjs13/tsconfig.json +++ b/examples/07-collaboration/11-versioning-yjs13/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/11-versioning-yjs13/vite.config.ts b/examples/07-collaboration/11-versioning-yjs13/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/11-versioning-yjs13/vite.config.ts +++ b/examples/07-collaboration/11-versioning-yjs13/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/12-multi-doc-versioning/tsconfig.json b/examples/07-collaboration/12-multi-doc-versioning/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/12-multi-doc-versioning/tsconfig.json +++ b/examples/07-collaboration/12-multi-doc-versioning/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/12-multi-doc-versioning/vite.config.ts b/examples/07-collaboration/12-multi-doc-versioning/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/12-multi-doc-versioning/vite.config.ts +++ b/examples/07-collaboration/12-multi-doc-versioning/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/13-versioning-yjs14/tsconfig.json b/examples/07-collaboration/13-versioning-yjs14/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/13-versioning-yjs14/tsconfig.json +++ b/examples/07-collaboration/13-versioning-yjs14/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/13-versioning-yjs14/vite.config.ts b/examples/07-collaboration/13-versioning-yjs14/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/13-versioning-yjs14/vite.config.ts +++ b/examples/07-collaboration/13-versioning-yjs14/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/07-collaboration/14-suggestion-gallery/.bnexample.json b/examples/07-collaboration/14-suggestion-gallery/.bnexample.json index 2e3d585a3a..e03899c16d 100644 --- a/examples/07-collaboration/14-suggestion-gallery/.bnexample.json +++ b/examples/07-collaboration/14-suggestion-gallery/.bnexample.json @@ -4,7 +4,6 @@ "author": "yousefed", "tags": ["Advanced", "Development", "Collaboration"], "dependencies": { - "@blocknote/shared": "latest", "@blocknote/xl-multi-column": "latest", "@y/protocols": "^1.0.6-rc.1", "@y/y": "^14.0.0-rc.23" diff --git a/examples/07-collaboration/14-suggestion-gallery/package.json b/examples/07-collaboration/14-suggestion-gallery/package.json index 28add5141b..34aeb8f0b1 100644 --- a/examples/07-collaboration/14-suggestion-gallery/package.json +++ b/examples/07-collaboration/14-suggestion-gallery/package.json @@ -20,7 +20,6 @@ "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", - "@blocknote/shared": "latest", "@blocknote/xl-multi-column": "latest", "@y/protocols": "^1.0.6-rc.1", "@y/y": "^14.0.0-rc.23" diff --git a/examples/07-collaboration/14-suggestion-gallery/src/scenarios.ts b/examples/07-collaboration/14-suggestion-gallery/src/scenarios.ts index f01d84cc60..e485ed3f87 100644 --- a/examples/07-collaboration/14-suggestion-gallery/src/scenarios.ts +++ b/examples/07-collaboration/14-suggestion-gallery/src/scenarios.ts @@ -1,4 +1,4 @@ -import { testDocument } from "@blocknote/shared/testDocument"; +import { testDocument } from "@shared/testDocument.js"; import type { GalleryEditor, GalleryPartialBlock } from "./gallerySchema"; diff --git a/examples/07-collaboration/14-suggestion-gallery/tsconfig.json b/examples/07-collaboration/14-suggestion-gallery/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/07-collaboration/14-suggestion-gallery/tsconfig.json +++ b/examples/07-collaboration/14-suggestion-gallery/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/07-collaboration/14-suggestion-gallery/vite.config.ts b/examples/07-collaboration/14-suggestion-gallery/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/07-collaboration/14-suggestion-gallery/vite.config.ts +++ b/examples/07-collaboration/14-suggestion-gallery/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json b/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json +++ b/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/vite.config.ts b/examples/08-extensions/01-tiptap-arrow-conversion/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/vite.config.ts +++ b/examples/08-extensions/01-tiptap-arrow-conversion/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/08-extensions/02-versioning/tsconfig.json b/examples/08-extensions/02-versioning/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/08-extensions/02-versioning/tsconfig.json +++ b/examples/08-extensions/02-versioning/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/08-extensions/02-versioning/vite.config.ts b/examples/08-extensions/02-versioning/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/08-extensions/02-versioning/vite.config.ts +++ b/examples/08-extensions/02-versioning/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/09-ai/01-minimal/tsconfig.json b/examples/09-ai/01-minimal/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/09-ai/01-minimal/tsconfig.json +++ b/examples/09-ai/01-minimal/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/09-ai/01-minimal/vite.config.ts b/examples/09-ai/01-minimal/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/09-ai/01-minimal/vite.config.ts +++ b/examples/09-ai/01-minimal/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/09-ai/02-playground/tsconfig.json b/examples/09-ai/02-playground/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/09-ai/02-playground/tsconfig.json +++ b/examples/09-ai/02-playground/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/09-ai/02-playground/vite.config.ts b/examples/09-ai/02-playground/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/09-ai/02-playground/vite.config.ts +++ b/examples/09-ai/02-playground/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/09-ai/03-custom-ai-menu-items/tsconfig.json b/examples/09-ai/03-custom-ai-menu-items/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/09-ai/03-custom-ai-menu-items/tsconfig.json +++ b/examples/09-ai/03-custom-ai-menu-items/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/09-ai/03-custom-ai-menu-items/vite.config.ts b/examples/09-ai/03-custom-ai-menu-items/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/09-ai/03-custom-ai-menu-items/vite.config.ts +++ b/examples/09-ai/03-custom-ai-menu-items/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/09-ai/04-with-collaboration/tsconfig.json b/examples/09-ai/04-with-collaboration/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/09-ai/04-with-collaboration/tsconfig.json +++ b/examples/09-ai/04-with-collaboration/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/09-ai/04-with-collaboration/vite.config.ts b/examples/09-ai/04-with-collaboration/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/09-ai/04-with-collaboration/vite.config.ts +++ b/examples/09-ai/04-with-collaboration/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/09-ai/05-manual-execution/tsconfig.json b/examples/09-ai/05-manual-execution/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/09-ai/05-manual-execution/tsconfig.json +++ b/examples/09-ai/05-manual-execution/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/09-ai/05-manual-execution/vite.config.ts b/examples/09-ai/05-manual-execution/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/09-ai/05-manual-execution/vite.config.ts +++ b/examples/09-ai/05-manual-execution/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/09-ai/06-client-side-transport/tsconfig.json b/examples/09-ai/06-client-side-transport/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/09-ai/06-client-side-transport/tsconfig.json +++ b/examples/09-ai/06-client-side-transport/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/09-ai/06-client-side-transport/vite.config.ts b/examples/09-ai/06-client-side-transport/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/09-ai/06-client-side-transport/vite.config.ts +++ b/examples/09-ai/06-client-side-transport/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/09-ai/07-server-persistence/tsconfig.json b/examples/09-ai/07-server-persistence/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/09-ai/07-server-persistence/tsconfig.json +++ b/examples/09-ai/07-server-persistence/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/09-ai/07-server-persistence/vite.config.ts b/examples/09-ai/07-server-persistence/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/09-ai/07-server-persistence/vite.config.ts +++ b/examples/09-ai/07-server-persistence/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json b/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json +++ b/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/vite.config.ts b/examples/vanilla-js/react-vanilla-custom-blocks/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/vite.config.ts +++ b/examples/vanilla-js/react-vanilla-custom-blocks/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/tsconfig.json b/examples/vanilla-js/react-vanilla-custom-inline-content/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/tsconfig.json +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/vite.config.ts b/examples/vanilla-js/react-vanilla-custom-inline-content/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/vite.config.ts +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json b/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json +++ b/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/vanilla-js/react-vanilla-custom-styles/vite.config.ts b/examples/vanilla-js/react-vanilla-custom-styles/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/vite.config.ts +++ b/examples/vanilla-js/react-vanilla-custom-styles/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/examples/vanilla-js/vanilla-custom-side-menu/tsconfig.json b/examples/vanilla-js/vanilla-custom-side-menu/tsconfig.json index 93fa81bee8..2aa62c56e6 100644 --- a/examples/vanilla-js/vanilla-custom-side-menu/tsconfig.json +++ b/examples/vanilla-js/vanilla-custom-side-menu/tsconfig.json @@ -15,7 +15,10 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "composite": true + "composite": true, + "paths": { + "@shared/*": ["../../../shared/*"] + } }, "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ diff --git a/examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts b/examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts index 95ed8cc314..a96f1f04ff 100644 --- a/examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts +++ b/examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts @@ -16,6 +16,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/package.json b/package.json index c6a2b830cd..0323f0ce02 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,14 @@ "name": "root", "type": "module", "devDependencies": { - "@typescript/native-preview": "7.0.0-dev.20260615.1", "bumpp": "^11.1.0", "changelogen": "^0.6.1", "concurrently": "9.1.2", "eslint-plugin-import": "^2.32.0", "glob": "^10.5.0", - "oxlint-tsgolint": "^0.23.0", + "oxlint-tsgolint": "^7.0.2001", "serve": "14.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:", "wait-on": "9.0.5" }, @@ -31,7 +30,7 @@ "e2e:updateSnaps": "bash tests/docker-run.sh -e CI=1 -- --run -u", "e2e:report": "serve -l 4173 tests/playwright-report", "lint": "vp lint --type-aware", - "typecheck": "tsgo --noEmit -p tsconfig.json", + "typecheck": "tsc --noEmit -p tsconfig.json", "postpublish": "rm -rf packages/core/README.md && rm -rf packages/react/README.md", "prebuild": "cp README.md packages/core/README.md && cp README.md packages/react/README.md", "prestart": "vp run build", diff --git a/packages/ariakit/package.json b/packages/ariakit/package.json index 70eabe70b5..7fb39e2050 100644 --- a/packages/ariakit/package.json +++ b/packages/ariakit/package.json @@ -67,7 +67,7 @@ "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plugin-externalize-deps": "^0.10.0", "vite-plus": "catalog:" }, diff --git a/packages/ariakit/vite.config.ts b/packages/ariakit/vite.config.ts index a53b363ac0..779d940bb7 100644 --- a/packages/ariakit/vite.config.ts +++ b/packages/ariakit/vite.config.ts @@ -12,7 +12,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/code-block/package.json b/packages/code-block/package.json index 6c6c60a25e..936a103efa 100644 --- a/packages/code-block/package.json +++ b/packages/code-block/package.json @@ -49,18 +49,18 @@ "clean": "rimraf dist && rimraf types" }, "dependencies": { - "@shikijs/core": "^4", - "@shikijs/engine-javascript": "^4", - "@shikijs/langs-precompiled": "^4", - "@shikijs/themes": "^4" + "@shikijs/core": "^4.4.3", + "@shikijs/engine-javascript": "^4.4.3", + "@shikijs/langs-precompiled": "^4.4.3", + "@shikijs/themes": "^4.4.3" }, "optionalDependencies": { - "@shikijs/types": "^4" + "@shikijs/types": "^4.4.3" }, "devDependencies": { "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { diff --git a/packages/code-block/src/index.test.ts b/packages/code-block/src/index.test.ts index f8a87bbdf4..8a83d858b5 100644 --- a/packages/code-block/src/index.test.ts +++ b/packages/code-block/src/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vite-plus/test"; -import { codeBlockOptions } from "./index.js"; +import { codeBlockOptions, syntaxHighlighter } from "./index.js"; describe("codeBlock", () => { it("should exist", () => { @@ -11,7 +11,10 @@ describe("codeBlock", () => { it("should have supportedLanguages", () => { expect(codeBlockOptions.supportedLanguages).toBeDefined(); }); - it("should have createHighlighter", () => { - expect(codeBlockOptions.createHighlighter).toBeDefined(); + it("should not configure a highlighter (that's now the syntaxHighlighter extension)", () => { + expect("createHighlighter" in codeBlockOptions).toBe(false); + }); + it("should export a pre-configured syntaxHighlighter extension", () => { + expect(syntaxHighlighter).toBeDefined(); }); }); diff --git a/packages/code-block/src/index.ts b/packages/code-block/src/index.ts index 2cb588092d..7cc905f662 100644 --- a/packages/code-block/src/index.ts +++ b/packages/code-block/src/index.ts @@ -1,6 +1,27 @@ import type { CodeBlockOptions } from "@blocknote/core"; +import { SyntaxHighlightingExtension } from "@blocknote/core"; import { createHighlighter } from "./shiki.bundle.js"; +/** + * A ready-to-use syntax highlighting extension, pre-configured with this + * package's bundled Shiki highlighter (the languages in `codeBlockOptions` and + * the `github-dark` / `github-light` themes). Add it to the editor's + * `extensions` to enable syntax highlighting for code blocks (and any other + * block that declares a language, such as the math block): + * + * @example + * ```ts + * useCreateBlockNote({ extensions: [syntaxHighlighter] }); + * ``` + */ +export const syntaxHighlighter = SyntaxHighlightingExtension({ + createHighlighter: () => + createHighlighter({ + themes: ["github-dark", "github-light"], + langs: [], + }), +}); + export const codeBlockOptions = { defaultLanguage: "javascript", supportedLanguages: { @@ -197,9 +218,4 @@ export const codeBlockOptions = { aliases: ["objective-c", "objc"], }, }, - createHighlighter: () => - createHighlighter({ - themes: ["github-dark", "github-light"], - langs: [], - }), } satisfies CodeBlockOptions; diff --git a/packages/code-block/vite.config.ts b/packages/code-block/vite.config.ts index 5f38b6f16a..e3a4990d36 100644 --- a/packages/code-block/vite.config.ts +++ b/packages/code-block/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/core/package.json b/packages/core/package.json index b928509796..145e7f44e0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -85,7 +85,7 @@ }, "scripts": { "dev": "vp dev", - "build-bundled": "tsgo && vp build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", + "build-bundled": "tsc && vp build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vp preview", "lint": "vp lint src", "test": "vp test --run", @@ -96,7 +96,7 @@ "dependencies": { "@emoji-mart/data": "^1.2.1", "@handlewithcare/prosemirror-inputrules": "^0.1.4", - "@shikijs/types": "^4", + "@shikijs/types": "^4.4.3", "@tiptap/core": "^3.29.2", "@tiptap/extension-bold": "^3.29.2", "@tiptap/extension-code": "^3.29.2", @@ -109,7 +109,7 @@ "emoji-mart": "^5.6.0", "fast-deep-equal": "^3.1.3", "lib0": "1.0.0-rc.22", - "prosemirror-highlight": "^0.15.1", + "prosemirror-highlight": "^0.15.3", "prosemirror-model": "^1.25.11", "prosemirror-state": "^1.4.4", "prosemirror-tables": "^1.8.5", @@ -120,37 +120,37 @@ "jsdom": "^29.0.2", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:", "y-prosemirror": "^1.3.7", "y-protocols": "^1.0.6", "yjs": "^13.6.27" }, "peerDependencies": { + "@y/prosemirror": "^2.0.0-6", + "@y/protocols": "^1.0.6-rc.1", + "@y/y": "^14.0.0-rc.23", "y-prosemirror": "^1.3.7", "y-protocols": "^1.0.6", - "yjs": "^13.6.27", - "@y/y": "^14.0.0-rc.23", - "@y/prosemirror": "^2.0.0-6", - "@y/protocols": "^1.0.6-rc.1" + "yjs": "^13.6.27" }, "peerDependenciesMeta": { - "y-prosemirror": { + "@y/y": { "optional": true }, - "y-protocols": { + "@y/prosemirror": { "optional": true }, - "yjs": { + "@y/protocols": { "optional": true }, - "@y/y": { + "y-prosemirror": { "optional": true }, - "@y/prosemirror": { + "y-protocols": { "optional": true }, - "@y/protocols": { + "yjs": { "optional": true } }, diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap index e4559884da..f98a42cfbf 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap @@ -3142,196 +3142,1780 @@ exports[`Test updateBlock > Update inline content to no content 2`] = ` ] `; -exports[`Test updateBlock > Update inline content to table content 1`] = ` +exports[`Test updateBlock > Update inline content to plain content 1`] = ` { "children": [], - "content": { - "columnWidths": [ - undefined, - undefined, - undefined, + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "language": "text", + }, + "type": "codeBlock", +} +`; + +exports[`Test updateBlock > Update inline content to plain content 2`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, ], - "headerCols": undefined, - "headerRows": undefined, - "rows": [ + "id": "paragraph-0", + "props": { + "language": "text", + }, + "type": "codeBlock", + }, + { + "children": [], + "content": [ { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ { + "children": [], "content": [ { "styles": {}, - "text": "Cell 2", + "text": "Double Nested Paragraph 0", "type": "text", }, ], + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, "textAlignment": "left", "textColor": "default", }, - "type": "tableCell", + "type": "paragraph", }, + ], + "content": [ { - "content": [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", }, ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", }, + ], + "content": [ { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update inline content to table content 1`] = ` +{ + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "paragraph-0", + "props": { + "textColor": "default", + }, + "type": "table", +} +`; + +exports[`Test updateBlock > Update inline content to table content 2`] = ` +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "paragraph-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ { + "children": [], "content": [ { "styles": {}, - "text": "Cell 5", + "text": "Double Nested Paragraph 1", "type": "text", }, ], + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, "textAlignment": "left", "textColor": "default", }, - "type": "tableCell", + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update no content to empty inline content 1`] = ` +{ + "children": [], + "content": [], + "id": "image-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", +} +`; + +exports[`Test updateBlock > Update no content to empty inline content 2`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ { + "children": [], "content": [ { "styles": {}, - "text": "Cell 6", + "text": "Double Nested Paragraph 0", "type": "text", }, ], + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, "textAlignment": "left", "textColor": "default", }, - "type": "tableCell", + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", }, ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - ], + "styles": {}, + "text": "Heading 1", + "type": "text", }, ], - "type": "tableContent", + "id": "heading-0", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", }, - "id": "paragraph-0", - "props": { - "textColor": "default", + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "image-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", }, - "type": "table", -} -`; - -exports[`Test updateBlock > Update inline content to table content 2`] = ` -[ { "children": [], "content": { @@ -3511,11 +5095,176 @@ exports[`Test updateBlock > Update inline content to table content 2`] = ` ], "type": "tableContent", }, + "id": "table-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update no content to empty table content 1`] = ` +{ + "children": [], + "content": { + "columnWidths": [], + "headerCols": undefined, + "headerRows": undefined, + "rows": [], + "type": "tableContent", + }, + "id": "image-0", + "props": { + "textColor": "default", + }, + "type": "table", +} +`; + +exports[`Test updateBlock > Update no content to empty table content 2`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], "id": "paragraph-0", "props": { + "backgroundColor": "default", + "textAlignment": "left", "textColor": "default", }, - "type": "table", + "type": "paragraph", }, { "children": [], @@ -3724,17 +5473,14 @@ exports[`Test updateBlock > Update inline content to table content 2`] = ` }, { "children": [], - "content": undefined, + "content": [], "id": "image-0", "props": { "backgroundColor": "default", - "caption": "", - "name": "", - "showPreview": true, "textAlignment": "left", - "url": "https://via.placeholder.com/150", + "textColor": "default", }, - "type": "image", + "type": "paragraph", }, { "children": [], @@ -4066,10 +5812,16 @@ exports[`Test updateBlock > Update inline content to table content 2`] = ` ] `; -exports[`Test updateBlock > Update no content to empty inline content 1`] = ` +exports[`Test updateBlock > Update no content to inline content 1`] = ` { "children": [], - "content": [], + "content": [ + { + "styles": {}, + "text": "Paragraph", + "type": "text", + }, + ], "id": "image-0", "props": { "backgroundColor": "default", @@ -4080,7 +5832,7 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` } `; -exports[`Test updateBlock > Update no content to empty inline content 2`] = ` +exports[`Test updateBlock > Update no content to inline content 2`] = ` [ { "children": [], @@ -4306,7 +6058,13 @@ exports[`Test updateBlock > Update no content to empty inline content 2`] = ` }, { "children": [], - "content": [], + "content": [ + { + "styles": {}, + "text": "Paragraph", + "type": "text", + }, + ], "id": "image-0", "props": { "backgroundColor": "default", @@ -4621,38 +6379,208 @@ exports[`Test updateBlock > Update no content to empty inline content 2`] = ` "type": "text", }, ], - "id": "heading-with-everything", - "props": { - "backgroundColor": "red", - "isToggleable": false, - "level": 2, - "textAlignment": "center", - "textColor": "red", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "paragraph-9", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Test updateBlock > Update no content to empty table content 1`] = ` -{ - "children": [], - "content": { - "columnWidths": [], - "headerCols": undefined, - "headerRows": undefined, - "rows": [], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update no content to table content 1`] = ` +{ + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "red", + "colspan": 1, + "rowspan": 1, + "textAlignment": "right", + "textColor": "red", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], "type": "tableContent", }, "id": "image-0", @@ -4663,7 +6591,7 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` } `; -exports[`Test updateBlock > Update no content to empty table content 2`] = ` +exports[`Test updateBlock > Update no content to table content 2`] = ` [ { "children": [], @@ -4889,14 +6817,188 @@ exports[`Test updateBlock > Update no content to empty table content 2`] = ` }, { "children": [], - "content": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "red", + "colspan": 1, + "rowspan": 1, + "textAlignment": "right", + "textColor": "red", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, "id": "image-0", "props": { - "backgroundColor": "default", - "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "table", }, { "children": [], @@ -5228,27 +7330,7 @@ exports[`Test updateBlock > Update no content to empty table content 2`] = ` ] `; -exports[`Test updateBlock > Update no content to inline content 1`] = ` -{ - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], - "id": "image-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", -} -`; - -exports[`Test updateBlock > Update no content to inline content 2`] = ` +exports[`Test updateBlock > Update partial (offset start + end) 1`] = ` [ { "children": [], @@ -5474,20 +7556,17 @@ exports[`Test updateBlock > Update no content to inline content 2`] = ` }, { "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], + "content": undefined, "id": "image-0", "props": { "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, "textAlignment": "left", - "textColor": "default", + "url": "https://via.placeholder.com/150", }, - "type": "paragraph", + "type": "image", }, { "children": [], @@ -5738,276 +7817,88 @@ exports[`Test updateBlock > Update no content to inline content 2`] = ` }, { "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Double Nested Paragraph 1", - "type": "text", - }, - ], - "id": "double-nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph 1", - "type": "text", - }, - ], - "id": "nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": { - "bold": true, - }, - "text": "Heading", - "type": "text", - }, - { - "styles": {}, - "text": " with styled ", - "type": "text", - }, - { - "styles": { - "italic": true, - }, - "text": "content", - "type": "text", - }, - ], - "id": "heading-with-everything", - "props": { - "backgroundColor": "red", - "isToggleable": false, - "level": 2, - "textAlignment": "center", - "textColor": "red", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "paragraph-9", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Test updateBlock > Update no content to table content 1`] = ` -{ - "children": [], - "content": { - "columnWidths": [ - undefined, - undefined, - undefined, - ], - "headerCols": undefined, - "headerRows": undefined, - "rows": [ - { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - "props": { - "backgroundColor": "red", - "colspan": 1, - "rowspan": 1, - "textAlignment": "right", - "textColor": "red", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - ], - }, - { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - ], - }, - { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, + { + "children": [ { + "children": [], "content": [ { "styles": {}, - "text": "Cell 9", + "text": "Double Nested Paragraph 1", "type": "text", }, ], + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, "textAlignment": "left", "textColor": "default", }, - "type": "tableCell", + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", }, ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", }, ], - "type": "tableContent", + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " without styles and with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", }, - "id": "image-0", - "props": { - "textColor": "default", + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", }, - "type": "table", -} +] `; -exports[`Test updateBlock > Update no content to table content 2`] = ` +exports[`Test updateBlock > Update partial (offset start) 1`] = ` [ { "children": [], @@ -6226,195 +8117,24 @@ exports[`Test updateBlock > Update no content to table content 2`] = ` "id": "paragraph-5", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": { - "columnWidths": [ - undefined, - undefined, - undefined, - ], - "headerCols": undefined, - "headerRows": undefined, - "rows": [ - { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - "props": { - "backgroundColor": "red", - "colspan": 1, - "rowspan": 1, - "textAlignment": "right", - "textColor": "red", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - ], - }, - { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - ], - }, - { - "cells": [ - { - "content": [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - { - "content": [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], - "props": { - "backgroundColor": "default", - "colspan": 1, - "rowspan": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "tableCell", - }, - ], - }, - ], - "type": "tableContent", + "textAlignment": "left", + "textColor": "default", }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, "id": "image-0", "props": { - "textColor": "default", + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", }, - "type": "table", + "type": "image", }, { "children": [], @@ -6711,14 +8431,7 @@ exports[`Test updateBlock > Update no content to table content 2`] = ` }, { "styles": {}, - "text": " with styled ", - "type": "text", - }, - { - "styles": { - "italic": true, - }, - "text": "content", + "text": " without styles", "type": "text", }, ], @@ -6746,7 +8459,7 @@ exports[`Test updateBlock > Update no content to table content 2`] = ` ] `; -exports[`Test updateBlock > Update partial (offset start + end) 1`] = ` +exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` [ { "children": [], @@ -7270,16 +8983,9 @@ exports[`Test updateBlock > Update partial (offset start + end) 1`] = ` }, ], "content": [ - { - "styles": { - "bold": true, - }, - "text": "Heading", - "type": "text", - }, { "styles": {}, - "text": " without styles and with styled ", + "text": "Title with styled ", "type": "text", }, { @@ -7294,7 +9000,7 @@ exports[`Test updateBlock > Update partial (offset start + end) 1`] = ` "props": { "backgroundColor": "red", "isToggleable": false, - "level": 2, + "level": 1, "textAlignment": "center", "textColor": "red", }, @@ -7314,7 +9020,7 @@ exports[`Test updateBlock > Update partial (offset start + end) 1`] = ` ] `; -exports[`Test updateBlock > Update partial (offset start) 1`] = ` +exports[`Test updateBlock > Update partial (table cell) 1`] = ` [ { "children": [], @@ -7586,7 +9292,7 @@ exports[`Test updateBlock > Update partial (offset start) 1`] = ` "content": [ { "styles": {}, - "text": "Cell 1", + "text": "updated cell 1", "type": "text", }, ], @@ -7847,7 +9553,14 @@ exports[`Test updateBlock > Update partial (offset start) 1`] = ` }, { "styles": {}, - "text": " without styles", + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", "type": "text", }, ], @@ -7875,7 +9588,7 @@ exports[`Test updateBlock > Update partial (offset start) 1`] = ` ] `; -exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` +exports[`Test updateBlock > Update partial (table row) 1`] = ` [ { "children": [], @@ -8147,7 +9860,7 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` "content": [ { "styles": {}, - "text": "Cell 1", + "text": "updated cell 1", "type": "text", }, ], @@ -8164,7 +9877,7 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` "content": [ { "styles": {}, - "text": "Cell 2", + "text": "updated cell 2", "type": "text", }, ], @@ -8181,7 +9894,7 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` "content": [ { "styles": {}, - "text": "Cell 3", + "text": "updated cell 3", "type": "text", }, ], @@ -8399,9 +10112,16 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` }, ], "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, { "styles": {}, - "text": "Title with styled ", + "text": " with styled ", "type": "text", }, { @@ -8416,7 +10136,7 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` "props": { "backgroundColor": "red", "isToggleable": false, - "level": 1, + "level": 2, "textAlignment": "center", "textColor": "red", }, @@ -8436,7 +10156,27 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` ] `; -exports[`Test updateBlock > Update partial (table cell) 1`] = ` +exports[`Test updateBlock > Update plain content to inline content 1`] = ` +{ + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", +} +`; + +exports[`Test updateBlock > Update plain content to inline content 2`] = ` [ { "children": [], @@ -8708,7 +10448,7 @@ exports[`Test updateBlock > Update partial (table cell) 1`] = ` "content": [ { "styles": {}, - "text": "updated cell 1", + "text": "Cell 1", "type": "text", }, ], @@ -9004,7 +10744,79 @@ exports[`Test updateBlock > Update partial (table cell) 1`] = ` ] `; -exports[`Test updateBlock > Update partial (table row) 1`] = ` +exports[`Test updateBlock > Update single prop 1`] = ` +{ + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 3, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", +} +`; + +exports[`Test updateBlock > Update single prop 2`] = ` [ { "children": [], @@ -9276,7 +11088,7 @@ exports[`Test updateBlock > Update partial (table row) 1`] = ` "content": [ { "styles": {}, - "text": "updated cell 1", + "text": "Cell 1", "type": "text", }, ], @@ -9293,7 +11105,7 @@ exports[`Test updateBlock > Update partial (table row) 1`] = ` "content": [ { "styles": {}, - "text": "updated cell 2", + "text": "Cell 2", "type": "text", }, ], @@ -9310,7 +11122,7 @@ exports[`Test updateBlock > Update partial (table row) 1`] = ` "content": [ { "styles": {}, - "text": "updated cell 3", + "text": "Cell 3", "type": "text", }, ], @@ -9552,7 +11364,7 @@ exports[`Test updateBlock > Update partial (table row) 1`] = ` "props": { "backgroundColor": "red", "isToggleable": false, - "level": 2, + "level": 3, "textAlignment": "center", "textColor": "red", }, @@ -9572,79 +11384,25 @@ exports[`Test updateBlock > Update partial (table row) 1`] = ` ] `; -exports[`Test updateBlock > Update single prop 1`] = ` +exports[`Test updateBlock > Update styled inline content to plain content 1`] = ` { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Double Nested Paragraph 1", - "type": "text", - }, - ], - "id": "double-nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph 1", - "type": "text", - }, - ], - "id": "nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], + "children": [], "content": [ - { - "styles": { - "bold": true, - }, - "text": "Heading", - "type": "text", - }, { "styles": {}, - "text": " with styled ", - "type": "text", - }, - { - "styles": { - "italic": true, - }, - "text": "content", + "text": "Paragraph with styled content", "type": "text", }, ], - "id": "heading-with-everything", + "id": "paragraph-with-styled-content", "props": { - "backgroundColor": "red", - "isToggleable": false, - "level": 3, - "textAlignment": "center", - "textColor": "red", + "language": "text", }, - "type": "heading", + "type": "codeBlock", } `; -exports[`Test updateBlock > Update single prop 2`] = ` +exports[`Test updateBlock > Update styled inline content to plain content 2`] = ` [ { "children": [], @@ -9787,33 +11545,17 @@ exports[`Test updateBlock > Update single prop 2`] = ` { "children": [], "content": [ - { - "styles": { - "bold": true, - }, - "text": "Paragraph", - "type": "text", - }, { "styles": {}, - "text": " with styled ", - "type": "text", - }, - { - "styles": { - "italic": true, - }, - "text": "content", + "text": "Paragraph with styled content", "type": "text", }, ], "id": "paragraph-with-styled-content", "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "language": "text", }, - "type": "paragraph", + "type": "codeBlock", }, { "children": [], @@ -10192,7 +11934,7 @@ exports[`Test updateBlock > Update single prop 2`] = ` "props": { "backgroundColor": "red", "isToggleable": false, - "level": 3, + "level": 2, "textAlignment": "center", "textColor": "red", }, diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts index 57aaf34fdd..e44e4a6380 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts @@ -475,6 +475,48 @@ describe("Test updateBlock", () => { expect(getEditor().document).toMatchSnapshot(); }); + it("Update inline content to plain content", () => { + expect( + getEditor().transact((tr) => + updateBlock(tr, "paragraph-0", { + type: "codeBlock", + }), + ), + ).toMatchSnapshot(); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update styled inline content to plain content", () => { + // The plain block can't hold formatting marks, so the styling is dropped + // but the text is preserved. + expect( + getEditor().transact((tr) => + updateBlock(tr, "paragraph-with-styled-content", { + type: "codeBlock", + }), + ), + ).toMatchSnapshot(); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update plain content to inline content", () => { + getEditor().transact((tr) => + updateBlock(tr, "paragraph-0", { type: "codeBlock" }), + ); + + expect( + getEditor().transact((tr) => + updateBlock(tr, "paragraph-0", { + type: "paragraph", + }), + ), + ).toMatchSnapshot(); + + expect(getEditor().document).toMatchSnapshot(); + }); + it("Update no content to empty inline content", () => { expect( getEditor().transact((tr) => diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 6ee03b04d9..6edfc434d5 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -202,14 +202,26 @@ function updateBlockContentNode< // Since some block types contain inline content and others don't, // we either need to call setNodeMarkup to just update type & // attributes, or replaceWith to replace the whole blockContent. + const oldContent = blockInfo.blockContent.node.content; if (oldNodeType.spec.content === "") { // keep old content, because it's empty anyway and should be compatible with // any newContentType - } else if (newNodeType.spec.content !== oldNodeType.spec.content) { - // the content type changed, replace the previous content - content = []; - } else { + } else if (newNodeType.spec.content === oldNodeType.spec.content) { // keep old content, because the content type is the same and should be compatible + } else if (newNodeType.validContent(oldContent)) { + // The content-type strings differ (e.g. "inline*" vs a plain block's + // "text*"), but the existing content is still valid for the new type + // (e.g. plain, unstyled text), so keep it rather than clearing it. + } else if (newNodeType.inlineContent && oldNodeType.inlineContent) { + // Both blocks hold text, but the existing content isn't directly valid + // for the new type (e.g. converting styled/complex inline content into a + // plain block that disallows formatting marks and inline nodes). Preserve + // the text, dropping the styling the new type can't represent. + const text = blockInfo.blockContent.node.textContent; + content = text.length > 0 ? [pmSchema.text(text)] : []; + } else { + // the content type changed and is incompatible, replace the previous content + content = []; } } diff --git a/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts b/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts index e74ecb68d3..553247fddb 100644 --- a/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts +++ b/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts @@ -78,6 +78,8 @@ function serializeNode(node: Node, ctx: SerializeContext): string { return serializeTable(el, ctx); case "hr": return ctx.indent + "***\n\n"; + case "math": + return serializeMathBlock(el, ctx); case "img": return serializeImage(el, ctx); case "video": @@ -175,18 +177,41 @@ function serializeCodeBlock(el: HTMLElement, ctx: SerializeContext): string { // For empty code blocks, don't add a newline between the fences if (!code) { - return ctx.indent + fence + language + "\n" + fence + "\n\n"; - } + return ctx.indent + fence + language + "\n" + ctx.indent + fence + "\n\n"; + } + + // Every (non-blank) line carries the indent - inside a list item or + // blockquote, an unindented line would end the parent container. Blank + // lines stay blank: they don't terminate an indented fence, and indenting + // them would add trailing whitespace. + const lines = [ + fence + language, + ...(code.endsWith("\n") ? code.slice(0, -1) : code).split("\n"), + fence, + ]; + return ( + lines.map((line) => (line ? ctx.indent + line : line)).join("\n") + "\n\n" + ); +} +// The LaTeX source of a MathML element, taken from the annotation KaTeX +// embeds in its output (also what external HTML parsing reads). Falls back to +// the element's text content for MathML from other sources. +function extractMathLatexSource(el: Element): string { + const annotation = el.querySelector( + 'annotation[encoding="application/x-tex"]', + ); + return (annotation?.textContent ?? el.textContent ?? "").trim(); +} + +function serializeMathBlock(el: HTMLElement, ctx: SerializeContext): string { + const latex = extractMathLatexSource(el); + // Every (non-blank) line carries the indent - inside a list item or + // blockquote, an unindented line would end the parent container. return ( - ctx.indent + - fence + - language + - "\n" + - code + - (code.endsWith("\n") ? "" : "\n") + - fence + - "\n\n" + ["$$", ...latex.split("\n"), "$$"] + .map((line) => (line ? ctx.indent + line : line)) + .join("\n") + "\n\n" ); } @@ -775,6 +800,16 @@ function serializeInlineContent(el: Element): string { case "br": result += "\\\n"; break; + case "math": { + // Inline math — emit its LaTeX source as a math span. Collapsed to + // a single line, as $...$ spans cannot contain newlines. + const latex = extractMathLatexSource(childEl) + .split("\n") + .map((line) => line.trim()) + .join(" "); + result += `$${latex}$`; + break; + } case "span": // Color spans, etc. — strip the tag, keep content result += serializeInlineContent(childEl); diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 61bc44d68a..af5c0ba1b7 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -64,7 +64,11 @@ function styledTextToNodes( ? [...schema.nodes[blockType].allowedMarks(marks)] : marks; - const parseHardBreaks = !blockType || !schema.nodes[blockType].spec.code; + // Plain content nodes hold raw text — including newlines — + // rather than inline content, so they can't contain `hardBreak` nodes. Keep + // newlines as text characters for them instead of splitting into hard breaks. + const parseHardBreaks = + !blockType || !isPlainContentNodeType(schema, schema.nodes[blockType]); if (!parseHardBreaks) { return styledText.text.length > 0 diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 6d3b7e23b2..fead006657 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -373,6 +373,9 @@ export function nodeToCustomInlineContent< inlineContentSchema, styleSchema, ) as any; // TODO: is this safe? could we have Links here that are undesired? + } else if (icConfig.content === "plain") { + // Plain inline content is a single unstyled string. + content = node.textContent as any; } else { content = undefined; } diff --git a/packages/core/src/api/pmUtil.ts b/packages/core/src/api/pmUtil.ts index bb427a3710..17ed2aa943 100644 --- a/packages/core/src/api/pmUtil.ts +++ b/packages/core/src/api/pmUtil.ts @@ -54,18 +54,26 @@ export function getBlockCache(schema: Schema) { } /** - * Whether `nodeType` is a BlockNote block whose content type is `"plain"` — i.e. - * it holds unstyled text and only allows the non-formatting (`"annotation"`) - * marks. Resolved semantically from the block schema (the source of truth), - * reachable from `schema.cached.blockNoteEditor`. + * Whether `nodeType` is a BlockNote block or inline content whose content type + * is `"plain"` — i.e. it holds unstyled text and only allows the non-formatting + * (`"annotation"`) marks. Resolved semantically from the block / inline content + * schema (the source of truth), reachable from `schema.cached.blockNoteEditor`. * - * Returns `false` for every non-block / structural node type (`doc`, - * `blockGroup`, `text`, inline content, table sub-nodes), since those aren't - * keys in the block schema. + * Returns `false` for every other node type (`doc`, `blockGroup`, `text`, table + * sub-nodes, and non-plain blocks / inline content), since those aren't plain + * content keys in either schema. */ export function isPlainContentNodeType( schema: Schema, nodeType: NodeType, ): boolean { - return getBlockSchema(schema)[nodeType.name]?.content === "plain"; + if (getBlockSchema(schema)[nodeType.name]?.content === "plain") { + return true; + } + + const inlineContentConfig = getInlineContentSchema(schema)[nodeType.name]; + return ( + typeof inlineContentConfig === "object" && + inlineContentConfig.content === "plain" + ); } diff --git a/packages/core/src/blocks/Code/CodeBlockOptions.ts b/packages/core/src/blocks/Code/CodeBlockOptions.ts new file mode 100644 index 0000000000..5f9435b0c4 --- /dev/null +++ b/packages/core/src/blocks/Code/CodeBlockOptions.ts @@ -0,0 +1,83 @@ +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import type { BlockFromConfig } from "../../schema/index.js"; + +/** + * Renders a preview of a code block's content (e.g. rendered LaTeX). Takes the + * same parameters as a block's `render` function and returns the same type, + * minus `contentDOM` - as a preview never holds the block's editable content. + * + * A `renderPreview` function is only responsible for the preview itself. It has + * no opinion on when, where, or how the preview is displayed - that's up to the + * code block's `render` function. + */ +export type CodeBlockPreview = ( + block: BlockFromConfig, + editor: BlockNoteEditor, +) => { + dom: HTMLElement | DocumentFragment; + error?: string | null; +}; + +export type CodeBlockOptions = { + /** + * Whether to indent lines with a tab when the user presses `Tab` in a code block. + * + * @default true + */ + indentLineWithTab?: boolean; + /** + * The default language to use for code blocks. + * + * @default "text" + */ + defaultLanguage?: string; + /** + * The languages that are supported in the editor. + * + * @example + * { + * javascript: { + * name: "JavaScript", + * aliases: ["js"], + * }, + * typescript: { + * name: "TypeScript", + * aliases: ["ts"], + * }, + * } + */ + supportedLanguages?: Record< + string, + { + /** + * The display name of the language. + */ + name: string; + /** + * Aliases for this language. + */ + aliases?: string[]; + /** + * Renders a preview of the result of the code (e.g. rendered LaTeX). When + * defined, the code block displays this preview instead of the raw source + * by default, and shows the editable source in a popup when selected. + */ + createPreview?: CodeBlockPreview; + } + >; +}; + +export function getLanguageId( + options: CodeBlockOptions, + languageName: string, +): string | undefined { + const normalizedLanguage = languageName.trim().toLowerCase(); + return Object.entries(options.supportedLanguages ?? {}).find( + ([id, { aliases }]) => { + return ( + id.toLowerCase() === normalizedLanguage || + aliases?.some((alias) => alias.toLowerCase() === normalizedLanguage) + ); + }, + )?.[0]; +} diff --git a/packages/core/src/blocks/Code/block.test.ts b/packages/core/src/blocks/Code/block.test.ts index 2bceaca629..9ce39df387 100644 --- a/packages/core/src/blocks/Code/block.test.ts +++ b/packages/core/src/blocks/Code/block.test.ts @@ -8,7 +8,7 @@ import { } from "vite-plus/test"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import type { PartialBlock } from "../defaultBlocks.js"; -import { getLanguageId, type CodeBlockOptions } from "./block.js"; +import { getLanguageId, type CodeBlockOptions } from "./CodeBlockOptions.js"; /** * @vitest-environment jsdom diff --git a/packages/core/src/blocks/Code/block.ts b/packages/core/src/blocks/Code/block.ts index 41fb17b61e..586d41e349 100644 --- a/packages/core/src/blocks/Code/block.ts +++ b/packages/core/src/blocks/Code/block.ts @@ -1,57 +1,14 @@ -import type { HighlighterGeneric } from "@shikijs/types"; -import { DOMParser } from "@tiptap/pm/model"; -import { createExtension } from "../../editor/BlockNoteExtension.js"; import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; -import { lazyShikiPlugin } from "./shiki.js"; +import { + parsePreCode, + parsePreCodeContent, +} from "./helpers/parse/parsePreCode.js"; +import { createPreCode } from "./helpers/toExternalHTML/createPreCode.js"; +import { CodeKeyboardShortcutsExtension } from "./helpers/extensions/CodeKeyboardShortcutsExtension.js"; +import { CodeBlockOptions } from "./CodeBlockOptions.js"; +import { createCodeBlock } from "./helpers/render/createCodeBlock.js"; -export type CodeBlockOptions = { - /** - * Whether to indent lines with a tab when the user presses `Tab` in a code block. - * - * @default true - */ - indentLineWithTab?: boolean; - /** - * The default language to use for code blocks. - * - * @default "text" - */ - defaultLanguage?: string; - /** - * The languages that are supported in the editor. - * - * @example - * { - * javascript: { - * name: "JavaScript", - * aliases: ["js"], - * }, - * typescript: { - * name: "TypeScript", - * aliases: ["ts"], - * }, - * } - */ - supportedLanguages?: Record< - string, - { - /** - * The display name of the language. - */ - name: string; - /** - * Aliases for this language. - */ - aliases?: string[]; - } - >; - /** - * The highlighter to use for code blocks. - */ - createHighlighter?: () => Promise>; -}; - -export type CodeBlockConfig = ReturnType; +const CODE_BLOCK_KEYBOARD_SHORTCUTS_KEY = "code-block-keyboard-shortcuts"; export const createCodeBlockConfig = createBlockConfig( ({ defaultLanguage = "text" }: CodeBlockOptions) => @@ -66,6 +23,8 @@ export const createCodeBlockConfig = createBlockConfig( }) as const, ); +export type CodeBlockConfig = ReturnType; + export const createCodeBlockSpec = createBlockSpec( createCodeBlockConfig, (options) => ({ @@ -73,236 +32,25 @@ export const createCodeBlockSpec = createBlockSpec( code: true, defining: true, isolating: false, + highlight: (block) => block.props.language, }, - parse: (e) => { - if (e.tagName !== "PRE") { - return undefined; - } - - if ( - e.childElementCount !== 1 || - e.firstElementChild?.tagName !== "CODE" - ) { - return undefined; - } - - const code = e.firstElementChild!; - const language = - code.getAttribute("data-language") || - code.className - .split(" ") - .find((name) => name.includes("language-")) - ?.replace("language-", ""); - - return { language }; - }, - - parseContent: ({ el, schema }) => { - const parser = DOMParser.fromSchema(schema); - const code = el.firstElementChild!; - - return parser.parse(code, { - preserveWhitespace: "full", - topNode: schema.nodes["codeBlock"].create(), - }).content; - }, - - render(block, editor) { - const wrapper = document.createDocumentFragment(); - const pre = document.createElement("pre"); - const code = document.createElement("code"); - pre.appendChild(code); - - let removeSelectChangeListener = undefined; - - if (options.supportedLanguages) { - const select = document.createElement("select"); - - Object.entries(options.supportedLanguages ?? {}).forEach( - ([id, { name }]) => { - const option = document.createElement("option"); - - option.value = id; - option.text = name; - select.appendChild(option); - }, - ); - select.value = - block.props.language || options.defaultLanguage || "text"; - - if (editor.isEditable) { - const handleLanguageChange = (event: Event) => { - const language = (event.target as HTMLSelectElement).value; - - editor.updateBlock(block.id, { props: { language } }); - }; - select.addEventListener("change", handleLanguageChange); - removeSelectChangeListener = () => - select.removeEventListener("change", handleLanguageChange); - } else { - select.disabled = true; - } - - const selectWrapper = document.createElement("div"); - selectWrapper.contentEditable = "false"; - - selectWrapper.appendChild(select); - wrapper.appendChild(selectWrapper); - } - wrapper.appendChild(pre); - - return { - dom: wrapper, - contentDOM: code, - destroy: () => { - removeSelectChangeListener?.(); + parse: (el) => parsePreCode(el), + parseContent: (opts) => parsePreCodeContent(opts, "codeBlock"), + render: (block, editor) => + createCodeBlock( + block, + editor, + options.supportedLanguages && { + selectedLanguage: block.props.language, + supportedLanguages: options.supportedLanguages, }, - }; - }, - toExternalHTML(block) { - const pre = document.createElement("pre"); - const code = document.createElement("code"); - code.className = `language-${block.props.language}`; - code.dataset.language = block.props.language; - pre.appendChild(code); - return { - dom: pre, - contentDOM: code, - }; - }, + ), + toExternalHTML: (block) => createPreCode(block), }), - (options) => { - return [ - createExtension({ - key: "code-block-highlighter", - prosemirrorPlugins: [lazyShikiPlugin(options)], - }), - createExtension({ - key: "code-block-keyboard-shortcuts", - keyboardShortcuts: { - Delete: ({ editor }) => { - return editor.transact((tr) => { - const { block } = editor.getTextCursorPosition(); - if (block.type !== "codeBlock") { - return false; - } - const { $from } = tr.selection; - - // When inside empty codeblock, on `DELETE` key press, delete the codeblock - if (!$from.parent.textContent) { - editor.removeBlocks([block]); - - return true; - } - - return false; - }); - }, - Tab: ({ editor }) => { - if (options.indentLineWithTab === false) { - return false; - } - - return editor.transact((tr) => { - const { block } = editor.getTextCursorPosition(); - if (block.type === "codeBlock") { - // TODO should probably only tab when at a line start or already tabbed in - tr.insertText(" "); - return true; - } - - return false; - }); - }, - Enter: ({ editor }) => { - return editor.transact((tr) => { - const { block, nextBlock } = editor.getTextCursorPosition(); - if (block.type !== "codeBlock") { - return false; - } - const { $from } = tr.selection; - - const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2; - const endsWithDoubleNewline = - $from.parent.textContent.endsWith("\n\n"); - - // The user is trying to exit the code block by pressing enter at the end of the code block - if (isAtEnd && endsWithDoubleNewline) { - // Remove the double newline - tr.delete($from.pos - 2, $from.pos); - - // If there is a next block, move the cursor to it - if (nextBlock) { - editor.setTextCursorPosition(nextBlock, "start"); - return true; - } - - // If there is no next block, insert a new paragraph - const [newBlock] = editor.insertBlocks( - [{ type: "paragraph" }], - block, - "after", - ); - // Move the cursor to the new block - editor.setTextCursorPosition(newBlock, "start"); - - return true; - } - - tr.insertText("\n"); - return true; - }); - }, - "Shift-Enter": ({ editor }) => { - return editor.transact(() => { - const { block } = editor.getTextCursorPosition(); - if (block.type !== "codeBlock") { - return false; - } - - const [newBlock] = editor.insertBlocks( - // insert a new paragraph - [{ type: "paragraph" }], - block, - "after", - ); - // move the cursor to the new block - editor.setTextCursorPosition(newBlock, "start"); - return true; - }); - }, - }, - inputRules: [ - { - find: /^```(.*?)\s$/, - replace: ({ match }) => { - const languageName = match[1].trim(); - const attributes = { - language: getLanguageId(options, languageName) ?? languageName, - }; - - return { - type: "codeBlock", - props: { - language: attributes.language, - }, - content: [], - }; - }, - }, - ], - }), - ]; - }, + (options) => [ + CodeKeyboardShortcutsExtension(options)( + CODE_BLOCK_KEYBOARD_SHORTCUTS_KEY, + "codeBlock", + ), + ], ); - -export function getLanguageId( - options: CodeBlockOptions, - languageName: string, -): string | undefined { - return Object.entries(options.supportedLanguages ?? {}).find( - ([id, { aliases }]) => { - return aliases?.includes(languageName) || id === languageName; - }, - )?.[0]; -} diff --git a/packages/core/src/blocks/Code/helpers/extensions/CodeKeyboardShortcutsExtension.ts b/packages/core/src/blocks/Code/helpers/extensions/CodeKeyboardShortcutsExtension.ts new file mode 100644 index 0000000000..f6b69f6c69 --- /dev/null +++ b/packages/core/src/blocks/Code/helpers/extensions/CodeKeyboardShortcutsExtension.ts @@ -0,0 +1,120 @@ +import { createExtension } from "../../../../editor/BlockNoteExtension.js"; +import { CodeBlockOptions, getLanguageId } from "../../CodeBlockOptions.js"; + +export const CodeKeyboardShortcutsExtension = + (options: CodeBlockOptions) => (key: string, blockType: string) => + createExtension({ + key, + keyboardShortcuts: { + Delete: ({ editor }) => { + return editor.transact((tr) => { + const { block } = editor.getTextCursorPosition(); + if (block.type !== blockType) { + return false; + } + const { $from } = tr.selection; + + // When inside empty codeblock, on `DELETE` key press, delete the codeblock + if (!$from.parent.textContent) { + editor.removeBlocks([block]); + + return true; + } + + return false; + }); + }, + Tab: ({ editor }) => { + if (options.indentLineWithTab === false) { + return false; + } + + return editor.transact((tr) => { + const { block } = editor.getTextCursorPosition(); + if (block.type === blockType) { + // TODO should probably only tab when at a line start or already tabbed in + tr.insertText(" "); + return true; + } + + return false; + }); + }, + Enter: ({ editor }) => { + return editor.transact((tr) => { + const { block, nextBlock } = editor.getTextCursorPosition(); + if (block.type !== blockType) { + return false; + } + const { $from } = tr.selection; + + const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2; + const endsWithDoubleNewline = + $from.parent.textContent.endsWith("\n\n"); + + // The user is trying to exit the code block by pressing enter at the end of the code block + if (isAtEnd && endsWithDoubleNewline) { + // Remove the double newline + tr.delete($from.pos - 2, $from.pos); + + // If there is a next block, move the cursor to it + if (nextBlock) { + editor.setTextCursorPosition(nextBlock, "start"); + return true; + } + + // If there is no next block, insert a new paragraph + const [newBlock] = editor.insertBlocks( + [{ type: "paragraph" }], + block, + "after", + ); + // Move the cursor to the new block + editor.setTextCursorPosition(newBlock, "start"); + + return true; + } + + tr.insertText("\n"); + return true; + }); + }, + "Shift-Enter": ({ editor }) => { + return editor.transact(() => { + const { block } = editor.getTextCursorPosition(); + if (block.type !== blockType) { + return false; + } + + const [newBlock] = editor.insertBlocks( + // insert a new paragraph + [{ type: "paragraph" }], + block, + "after", + ); + // move the cursor to the new block + editor.setTextCursorPosition(newBlock, "start"); + return true; + }); + }, + }, + inputRules: [ + { + find: /^```(.*?)\s$/, + replace: ({ match }) => { + const languageName = match[1].trim(); + const attributes = { + language: getLanguageId(options, languageName) ?? languageName, + }; + + return { + type: blockType, + props: { + language: attributes.language, + }, + content: [], + }; + }, + }, + ], + }); diff --git a/packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts b/packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts new file mode 100644 index 0000000000..237462fdb6 --- /dev/null +++ b/packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts @@ -0,0 +1,45 @@ +import { DOMParser, Schema } from "@tiptap/pm/model"; + +export const parsePreCode = (el: HTMLElement) => { + { + if (el.tagName !== "PRE") { + return undefined; + } + + if ( + el.childElementCount !== 1 || + el.firstElementChild?.tagName !== "CODE" + ) { + return undefined; + } + + const code = el.firstElementChild!; + const language = + code.getAttribute("data-language") || + code.className + .split(" ") + .find((name) => name.startsWith("language-")) + ?.replace("language-", ""); + + return { language }; + } +}; + +export const parsePreCodeContent = ( + { + el, + schema, + }: { + el: HTMLElement; + schema: Schema; + }, + blockType: string, +) => { + const parser = DOMParser.fromSchema(schema); + const code = el.firstElementChild!; + + return parser.parse(code, { + preserveWhitespace: "full", + topNode: schema.nodes[blockType].create(), + }).content; +}; diff --git a/packages/core/src/blocks/Code/helpers/render/createCodeBlock.ts b/packages/core/src/blocks/Code/helpers/render/createCodeBlock.ts new file mode 100644 index 0000000000..ed1e73edf3 --- /dev/null +++ b/packages/core/src/blocks/Code/helpers/render/createCodeBlock.ts @@ -0,0 +1,98 @@ +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; +import type { BlockFromConfig } from "../../../../schema/index.js"; + +// Select dropdown to change the block's language. Assumes `block` has a `language` prop. +export const createLanguageSelect = ( + block: BlockFromConfig, + editor: BlockNoteEditor, + selectedLanguage: string, + supportedLanguages: Record< + string, + { + name: string; + } + >, +) => { + if (!(selectedLanguage in supportedLanguages)) { + throw new Error(`Language ${selectedLanguage} is not supported.`); + } + + const select = document.createElement("select"); + Object.entries(supportedLanguages).forEach(([id, { name }]) => { + const option = document.createElement("option"); + option.value = id; + option.text = name; + select.appendChild(option); + }); + select.value = selectedLanguage; + + const handleLanguageChange = (event: Event) => { + if (!editor.isEditable) { + return; + } + + editor.updateBlock(block.id, { + props: { language: (event.target as HTMLSelectElement).value }, + }); + }; + + if (editor.isEditable) { + select.addEventListener("change", handleLanguageChange); + } else { + select.disabled = true; + } + + const selectWrapper = document.createElement("div"); + selectWrapper.contentEditable = "false"; + selectWrapper.appendChild(select); + + return { + dom: selectWrapper, + destroy: () => select.removeEventListener("change", handleLanguageChange), + }; +}; + +// Renders the block's inline content as code, alongside a language picker, if multiple languages +// are supported. +export const createCodeBlock = ( + block: BlockFromConfig, + editor: BlockNoteEditor, + options?: { + selectedLanguage: string; + supportedLanguages: Record< + string, + { + name: string; + } + >; + }, +) => { + const pre = document.createElement("pre"); + const code = document.createElement("code"); + pre.appendChild(code); + + const sourceBlock = document.createDocumentFragment(); + + let languageSelect: ReturnType | undefined = + undefined; + if (options && Object.keys(options.supportedLanguages).length > 1) { + languageSelect = createLanguageSelect( + block, + editor, + options.selectedLanguage, + options.supportedLanguages, + ); + + sourceBlock.appendChild(languageSelect.dom); + } + + sourceBlock.appendChild(pre); + + return { + dom: sourceBlock, + contentDOM: code, + destroy: () => { + languageSelect?.destroy(); + }, + }; +}; diff --git a/packages/core/src/blocks/Code/helpers/toExternalHTML/createPreCode.ts b/packages/core/src/blocks/Code/helpers/toExternalHTML/createPreCode.ts new file mode 100644 index 0000000000..1b53828585 --- /dev/null +++ b/packages/core/src/blocks/Code/helpers/toExternalHTML/createPreCode.ts @@ -0,0 +1,14 @@ +import type { BlockFromConfig } from "../../../../schema/index.js"; + +export const createPreCode = (block: BlockFromConfig) => { + const pre = document.createElement("pre"); + const code = document.createElement("code"); + code.className = `language-${block.props.language}`; + code.dataset.language = block.props.language; + pre.appendChild(code); + + return { + dom: pre, + contentDOM: code, + }; +}; diff --git a/packages/core/src/blocks/Code/shiki.ts b/packages/core/src/blocks/Code/shiki.ts deleted file mode 100644 index 1298007a58..0000000000 --- a/packages/core/src/blocks/Code/shiki.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { HighlighterGeneric } from "@shikijs/types"; -import { Parser, createHighlightPlugin } from "prosemirror-highlight"; -import { createParser } from "prosemirror-highlight/shiki"; -import { CodeBlockOptions, getLanguageId } from "./block.js"; - -export const shikiParserSymbol = Symbol.for("blocknote.shikiParser"); -export const shikiHighlighterPromiseSymbol = Symbol.for( - "blocknote.shikiHighlighterPromise", -); - -export function lazyShikiPlugin(options: CodeBlockOptions) { - const globalThisForShiki = globalThis as { - [shikiHighlighterPromiseSymbol]?: Promise>; - [shikiParserSymbol]?: Parser; - }; - - let highlighter: HighlighterGeneric | undefined; - let parser: Parser | undefined; - let hasWarned = false; - const lazyParser: Parser = (parserOptions) => { - if (!options.createHighlighter) { - if (process.env.NODE_ENV === "development" && !hasWarned) { - // eslint-disable-next-line no-console - console.log( - "For syntax highlighting of code blocks, you must provide a `createCodeBlockSpec({ createHighlighter: () => ... })` function", - ); - hasWarned = true; - } - return []; - } - if (!highlighter) { - globalThisForShiki[shikiHighlighterPromiseSymbol] = - globalThisForShiki[shikiHighlighterPromiseSymbol] || - options.createHighlighter(); - - return globalThisForShiki[shikiHighlighterPromiseSymbol].then( - (createdHighlighter) => { - highlighter = createdHighlighter; - }, - ); - } - const language = getLanguageId(options, parserOptions.language!); - - if ( - !language || - language === "text" || - language === "none" || - language === "plaintext" || - language === "txt" - ) { - return []; - } - - if (!highlighter.getLoadedLanguages().includes(language)) { - return highlighter.loadLanguage(language); - } - - if (!parser) { - parser = - globalThisForShiki[shikiParserSymbol] || - createParser(highlighter as any); - globalThisForShiki[shikiParserSymbol] = parser; - } - - return parser(parserOptions); - }; - - return createHighlightPlugin({ - parser: lazyParser, - languageExtractor: (node) => node.attrs.language, - nodeTypes: ["codeBlock"], - }); -} diff --git a/packages/core/src/blocks/index.ts b/packages/core/src/blocks/index.ts index 56f4c6de3c..d40bba055c 100644 --- a/packages/core/src/blocks/index.ts +++ b/packages/core/src/blocks/index.ts @@ -16,6 +16,9 @@ export * from "./Table/block.js"; export * from "./Video/block.js"; export { EMPTY_CELL_HEIGHT, EMPTY_CELL_WIDTH } from "./Table/TableExtension.js"; +export * from "./Code/helpers/parse/parsePreCode.js"; +export * from "./Code/helpers/render/createCodeBlock.js"; +export * from "./Code/helpers/toExternalHTML/createPreCode.js"; export * from "./ToggleWrapper/createToggleWrapper.js"; export * from "./File/helpers/uploadToTmpFilesDotOrg_DEV_ONLY.js"; export * from "./PageBreak/getPageBreakSlashMenuItems.js"; diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 80a707f02e..ef2867121d 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -22,9 +22,27 @@ BASIC STYLES .bn-block-content.ProseMirror-selectednode > *, /* Case for node view renderers */ -.ProseMirror-selectednode > .bn-block-content > * { +.ProseMirror-selectednode > .bn-block-content > *, +/* Case for blocks/inline content where class is set manually */ +.bn-block-content .ProseMirror-selectednode, +.bn-inline-content .ProseMirror-selectednode { + /* Anchor for the `::after` highlight overlay below. */ + position: relative; +} + +/* Draws the selection highlight (border + translucent fill) as an overlay on + top of the element's content */ +.bn-block-content.ProseMirror-selectednode > *::after, +.ProseMirror-selectednode > .bn-block-content > *::after, +.bn-block-content .ProseMirror-selectednode::after, +.bn-inline-content .ProseMirror-selectednode::after { + content: ""; + position: absolute; + inset: 0; border-radius: 4px; - outline: 4px solid rgb(100, 160, 255); + background-color: rgba(100, 160, 255, 0.08); + box-shadow: inset 0 0 0 4px rgba(100, 160, 255, 0.3); + pointer-events: none; } .bn-block-content::before { @@ -468,6 +486,259 @@ NESTED BLOCKS transition-delay: 0.1s; } +/* CODE BLOCK PREVIEW */ +.bn-block-content[data-content-type="codeBlock"]:has( + .bn-preview-with-source-popup + ) { + background-color: transparent; + color: inherit; +} + +/* Default to dark theme as the code block has a dark background regardless of theme. */ +.shiki { + color: var(--shiki-dark); +} +.bn-source-block-popup .shiki { + color: var(--shiki-light); +} +.bn-root[data-color-scheme="dark"] .bn-source-block-popup .shiki { + color: var(--shiki-dark); +} + +.bn-preview-with-source-popup { + position: relative; +} + +.bn-block-content .bn-preview-with-source-popup { + display: flex; + padding: 12px; + width: 100%; +} + +.bn-inline-content-section .bn-preview-with-source-popup { + display: inline-block; + padding: 0; + width: fit-content; +} + +.bn-preview-container { + anchor-name: --bn-source-popup-anchor; + cursor: pointer; +} + +.bn-block-content .bn-preview-container { + overflow: auto; + width: 100%; +} + +.bn-inline-content-section .bn-preview-container { + overflow: visible; + width: fit-content; +} + +/* Additional right padding for equation numbers. */ +.bn-block-content .bn-preview-container .katex-html:has(> .tag) { + box-sizing: border-box; + width: max-content; + min-width: 100%; + padding-right: 2.5em; +} + +.bn-source-block-popup { + background-color: transparent; + border: none; + border-radius: 0; + box-shadow: none; + color: transparent; + height: 0; + left: 0; + margin-top: 4px; + overflow: clip; + position: absolute; + top: 100%; + width: 0; + z-index: 0; +} + +.bn-preview-with-source-popup[data-open="true"] .bn-source-block-popup { + background-color: var(--bn-colors-menu-background); + border: var(--bn-border); + border-radius: var(--bn-border-radius-medium); + box-shadow: var(--bn-shadow-medium); + color: var(--bn-colors-menu-text); + height: unset; + z-index: 1; +} + +.bn-block-content + .bn-preview-with-source-popup[data-open="true"] + .bn-source-block-popup { + width: 100%; +} + +.bn-inline-content-section + .bn-preview-with-source-popup[data-open="true"] + .bn-source-block-popup { + width: 300px; +} + +.bn-code-block-source-popup-body { + align-items: flex-end; + display: flex; +} + +.bn-code-block-source-popup-body > pre { + align-self: center; + flex: 1; + margin: 0; + max-height: 200px; + overflow: auto; + padding: 16px; + position: relative; + tab-size: 2; + white-space: pre; + width: 0; +} + +.bn-code-block-source-popup-input-empty::before { + color: currentColor; + content: attr(data-placeholder); + left: 16px; + opacity: 0.5; + overflow: hidden; + pointer-events: none; + position: absolute; + right: 16px; + text-overflow: ellipsis; + top: 16px; + white-space: nowrap; +} + +.bn-code-block-source-error { + border-top: var(--bn-border); + color: var(--bn-colors-highlights-red-text); + font-size: 0.8em; + padding: 8px 16px; + white-space: pre-wrap !important; + font-family: monospace; +} + +.bn-code-block-source-popup-ok-button-wrapper { + align-items: flex-end; + justify-content: flex-end; + padding: 14px 16px; +} + +.bn-code-block-source-popup-ok-button { + align-items: center; + appearance: none; + background-color: rgb(37, 99, 235); + border: none; + border-radius: var(--bn-border-radius-small); + color: white; + cursor: pointer; + display: flex; + font-size: 0.8em; + font-weight: 500; + gap: 4px; + padding: 4px 8px; +} + +.bn-code-block-source-popup-ok-button-icon { + height: 1.2em; + width: 1.2em; +} + +.bn-code-block-source-popup-ok-button:hover { + background-color: rgb(29, 78, 216); +} + +.bn-preview-with-source-popup:has(.bn-preview-placeholder) { + padding: 0; +} + +.bn-preview-placeholder { + align-items: center; + background-color: rgb(242, 241, 238); + border-radius: 4px; + color: rgb(95, 91, 92); + display: flex; +} + +.bn-block-content .bn-preview-placeholder { + gap: 10px; + padding: 12px; +} + +.bn-inline-content-section .bn-preview-placeholder { + gap: 4px; + padding: 0 4px; +} + +.bn-preview-placeholder:where(.dark, .dark *) { + background-color: rgb(70, 70, 70); + color: rgb(190, 190, 190); +} + +.bn-editor[contenteditable="true"] + .bn-preview-placeholder:not(.bn-preview-placeholder-error):hover { + background-color: rgb(225, 225, 225); +} + +.bn-editor[contenteditable="true"] + .bn-preview-placeholder:not(.bn-preview-placeholder-error):hover:where( + .dark, + .dark * + ) { + background-color: rgb(90, 90, 90); +} + +.bn-preview-placeholder-error { + background-color: var(--bn-colors-highlights-red-background); + color: var(--bn-colors-highlights-red-text); +} + +.bn-preview-placeholder-icon { + align-items: center; + display: flex; +} + +.bn-block-content .bn-preview-placeholder-icon { + height: 24px; + width: 24px; +} + +.bn-inline-content-section .bn-preview-placeholder-icon { + height: 14px; + width: 14px; +} + +.bn-preview-placeholder-icon > svg { + height: 100%; + width: 100%; +} + +.bn-preview-placeholder-text { + margin: 0; +} + +.bn-block-content .bn-preview-placeholder-text { + font-size: 0.9rem; +} + +.bn-inline-content-section .bn-preview-placeholder-text { + font-size: 0.7rem; +} + +/* Toggled for a single frame while an up/down arrow press is handled, so the + browser's geometry-based vertical caret movement skips the popup. */ +.bn-suppress-source-popup-caret + .bn-inline-content-section + .bn-preview-with-source-popup:not([data-open="true"]) + .bn-source-block-popup { + visibility: hidden; +} + /* PAGE BREAK */ .bn-block-content[data-content-type="pageBreak"] > div { width: 100%; diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 0f8c24f6b7..25b93d03f4 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -41,7 +41,11 @@ import "../style.css"; import { mergeCSSClasses } from "../util/browser.js"; import { EventEmitter } from "../util/EventEmitter.js"; import type { NoInfer } from "../util/typescript.js"; -import { ExtensionFactoryInstance } from "./BlockNoteExtension.js"; +import { + Extension, + ExtensionFactory, + ExtensionFactoryInstance, +} from "./BlockNoteExtension.js"; import type { TextCursorPosition } from "./cursorPositionTypes.js"; import { BlockManager, @@ -690,9 +694,26 @@ export class BlockNoteEditor< /** * Get an extension from the editor */ - public getExtension: ExtensionManager["getExtension"] = (( - ...args: Parameters - ) => this._extensionManager.getExtension(...args)) as any; + // Declared as an explicit intersection of the two `ExtensionManager` + // overloads rather than `ExtensionManager["getExtension"]`: indexed access on + // an overloaded method collapses the signatures, which widened the factory + // overload's `ReturnType>` result to `any` (losing e.g. a + // returned extension's `store` type). + public getExtension: (< + const Ext extends Extension | ExtensionFactory = Extension, + >( + extension: string, + ) => + | (Ext extends Extension + ? Ext + : Ext extends ExtensionFactory + ? ReturnType> + : never) + | undefined) & + (( + extension: T, + ) => ReturnType> | undefined) = ((extension: any) => + this._extensionManager.getExtension(extension)) as any; /** * Mount the editor to a DOM element. diff --git a/packages/core/src/editor/managers/ExtensionManager/extensions.ts b/packages/core/src/editor/managers/ExtensionManager/extensions.ts index 2592b25d2a..853cca2493 100644 --- a/packages/core/src/editor/managers/ExtensionManager/extensions.ts +++ b/packages/core/src/editor/managers/ExtensionManager/extensions.ts @@ -15,6 +15,7 @@ import { FilePanelExtension, FormattingToolbarExtension, HistoryExtension, + InlineContentBoundaryEditExtension, LinkToolbarExtension, NodeSelectionKeyboardExtension, PlaceholderExtension, @@ -22,6 +23,8 @@ import { PreviousBlockTypeExtension, ShowSelectionExtension, SideMenuExtension, + SourceBlockWithPreviewExtension, + SourceInlineContentWithPreviewExtension, SuggestionMenu, TableHandlesExtension, TrailingNodeExtension, @@ -167,8 +170,11 @@ export function getDefaultExtensions( PlaceholderExtension(options), ShowSelectionExtension(options), SideMenuExtension(options), + SourceBlockWithPreviewExtension(), + SourceInlineContentWithPreviewExtension(), SuggestionMenu(options), HistoryExtension(), + InlineContentBoundaryEditExtension(), PositionMappingExtension(), ...(options.trailingBlock !== false ? [TrailingNodeExtension()] : []), ] as ExtensionFactoryInstance[]; diff --git a/packages/core/src/exporter/ExportImage.ts b/packages/core/src/exporter/ExportImage.ts new file mode 100644 index 0000000000..8d7fb30c07 --- /dev/null +++ b/packages/core/src/exporter/ExportImage.ts @@ -0,0 +1,50 @@ +/** + * An image generated during export (e.g. a rendered formula or diagram): the + * encoded image bytes plus the dimensions to display it at. + * + * The bytes (rather than e.g. a data URL string or a `Blob`) are the source + * of truth: they carry no encoding ambiguity, work in every environment, and + * are readable synchronously - each output format converts them at its own + * boundary (data URL for HTML-based targets, raw bytes for DOCX, base64 for + * email attachments). + */ +export type ExportImage = { + /** MIME type of `data`, e.g. `"image/png"` or `"image/svg+xml"`. */ + mimeType: string; + /** The encoded image bytes. */ + data: Uint8Array; + /** + * Dimensions to display the image at, in the target format's units (CSS + * pixels, points, ...). For raster images, `data`'s own pixel dimensions + * may be larger - images are often rendered at 2-4x for sharpness. + */ + width: number; + height: number; +}; + +/** + * Encodes bytes as base64. This papers over a platform gap: until + * `Uint8Array.prototype.toBase64()` (ES2026) is available in every runtime + * BlockNote supports, the only universal built-in is `btoa`, which takes + * binary *strings*. Uses `toBase64` when the runtime has it. + */ +export function bytesToBase64(bytes: Uint8Array): string { + if ("toBase64" in bytes && typeof bytes.toBase64 === "function") { + return (bytes as Uint8Array & { toBase64(): string }).toBase64(); + } + + let binary = ""; + for (const byte of bytes) { + binary += String.fromCharCode(byte); + } + return btoa(binary); +} + +/** + * Encodes an {@link ExportImage}'s bytes as a base64 data URL, for targets + * that take image sources as URLs (HTML `src` attributes, react-pdf image + * sources, ...). + */ +export function exportImageToDataURL(image: ExportImage): string { + return `data:${image.mimeType};base64,${bytesToBase64(image.data)}`; +} diff --git a/packages/core/src/exporter/Exporter.test.ts b/packages/core/src/exporter/Exporter.test.ts new file mode 100644 index 0000000000..e1fd22d9e8 --- /dev/null +++ b/packages/core/src/exporter/Exporter.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { BlockNoteSchema } from "../blocks/BlockNoteSchema.js"; +import { COLORS_DEFAULT } from "../editor/defaultColors.js"; +import { StyledText } from "../schema/index.js"; +import { Exporter } from "./Exporter.js"; + +// A minimal concrete exporter with empty mappings, to exercise the +// missing-mapping errors thrown when a document contains block types the +// mappings don't cover (e.g. blocks from separate packages, like math, +// without their exporter mappings spread in). +class TestExporter extends Exporter { + constructor() { + super( + BlockNoteSchema.create(), + { blockMapping: {}, inlineContentMapping: {}, styleMapping: {} } as any, + { colors: COLORS_DEFAULT }, + ); + } + + public transformStyledText(_styledText: StyledText) { + return undefined; + } +} + +describe("Exporter missing mappings", () => { + it("throws a descriptive error for an unmapped block type", async () => { + await expect( + new TestExporter().mapBlock({ type: "math" } as any, 0, 0), + ).rejects.toThrow( + 'missing a block mapping for block type "math". If this block comes from a separate package, spread that package\'s exporter mappings', + ); + }); + + it("throws a descriptive error for an unmapped inline content type", () => { + expect(() => + new TestExporter().mapInlineContent({ type: "inlineMath" } as any), + ).toThrow( + 'missing an inline content mapping for inline content type "inlineMath"', + ); + }); + + it("throws a descriptive error for an unmapped style", () => { + expect(() => new TestExporter().mapStyles({ bold: true } as any)).toThrow( + 'missing a style mapping for style "bold"', + ); + }); +}); diff --git a/packages/core/src/exporter/Exporter.ts b/packages/core/src/exporter/Exporter.ts index f42e89e6f4..9c7a2650fd 100644 --- a/packages/core/src/exporter/Exporter.ts +++ b/packages/core/src/exporter/Exporter.ts @@ -1,5 +1,7 @@ import { BlockNoteSchema } from "../blocks/BlockNoteSchema.js"; import { COLORS_DEFAULT } from "../editor/defaultColors.js"; +import type { Dictionary } from "../i18n/dictionary.js"; +import { en } from "../i18n/locales/index.js"; import { BlockFromConfig, BlockSchema, @@ -34,6 +36,20 @@ export type ExporterOptions = { * Colors to use for background of blocks, font colors, and highlight colors */ colors: typeof COLORS_DEFAULT; + /** + * The strings an exporter renders into the produced document (file link + * texts, error placeholders). Accepts a locale from + * `@blocknote/core/locales` or an editor dictionary; block packages that + * ship their own exporter strings (e.g. math, diagram) read their sections + * from this same object, exactly as they do from an editor dictionary. + * + * @default the English strings + */ + dictionary?: { exporter: Dictionary["exporter"] } & { + // Block packages read their own sections (e.g. `math`, `diagram`) from + // the same object; their types live with those packages. + [blockDictionary: string]: unknown; + }; }; export abstract class Exporter< B extends BlockSchema, @@ -54,6 +70,15 @@ export abstract class Exporter< public readonly options: ExporterOptions, ) {} + /** + * The strings this exporter renders into the produced document - the + * `exporter` section of the configured dictionary (the `dictionary` + * option of {@link ExporterOptions}), or the English defaults. + */ + public get dictionary(): Dictionary["exporter"] { + return this.options.dictionary?.exporter ?? en.exporter; + } + public async resolveFile(url: string) { if (!this.options?.resolveFileUrl) { return (await fetch(url)).blob(); @@ -67,17 +92,26 @@ export abstract class Exporter< public mapStyles(styles: Styles) { const stylesArray = Object.entries(styles).map(([key, value]) => { - const mappedStyle = this.mappings.styleMapping[key](value, this); + const mapping = this.mappings.styleMapping[key]; + if (!mapping) { + throw new Error( + `Exporter is missing a style mapping for style "${key}". If this style comes from a separate package, spread that package's exporter mappings into your styleMapping.`, + ); + } + const mappedStyle = mapping(value, this); return mappedStyle; }); return stylesArray; } public mapInlineContent(inlineContent: InlineContent) { - return this.mappings.inlineContentMapping[inlineContent.type]( - inlineContent, - this, - ); + const mapping = this.mappings.inlineContentMapping[inlineContent.type]; + if (!mapping) { + throw new Error( + `Exporter is missing an inline content mapping for inline content type "${inlineContent.type}". If this inline content comes from a separate package, spread that package's exporter mappings into your inlineContentMapping.`, + ); + } + return mapping(inlineContent, this); } public transformInlineContent(inlineContentArray: InlineContent[]) { @@ -92,12 +126,12 @@ export abstract class Exporter< numberedListIndex: number, children?: Array>, ) { - return this.mappings.blockMapping[block.type]( - block, - this, - nestingLevel, - numberedListIndex, - children, - ); + const mapping = this.mappings.blockMapping[block.type]; + if (!mapping) { + throw new Error( + `Exporter is missing a block mapping for block type "${block.type}". If this block comes from a separate package, spread that package's exporter mappings into your blockMapping.`, + ); + } + return mapping(block, this, nestingLevel, numberedListIndex, children); } } diff --git a/packages/core/src/exporter/index.ts b/packages/core/src/exporter/index.ts index e9d6a7bb03..8dcf4c2c2b 100644 --- a/packages/core/src/exporter/index.ts +++ b/packages/core/src/exporter/index.ts @@ -1,2 +1,3 @@ export * from "./Exporter.js"; +export * from "./ExportImage.js"; export * from "./mapping.js"; diff --git a/packages/core/src/exporter/mapping.ts b/packages/core/src/exporter/mapping.ts index 0dca63ebc3..fbe88d693e 100644 --- a/packages/core/src/exporter/mapping.ts +++ b/packages/core/src/exporter/mapping.ts @@ -42,7 +42,10 @@ export type InlineContentMapping< > = { [K in keyof I]: ( inlineContent: InlineContentFromConfig, - exporter: Exporter, + // Deliberately loose on the schema generics, like `BlockMapping` above - + // otherwise a mapping declared for one schema can't be reused (e.g. + // spread) in a mapping for a schema with different types. + exporter: Exporter, ) => RI; }; diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts index 84524eee2e..429624cc8e 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts @@ -24,18 +24,20 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { return false; } - // Searches the content of the selection to see if it spans a node with a - // code spec. - let spansCode = false; + // Searches the content of the selection to see if it spans a node with + // `"plain"` content (mapped to a `"text*"` node spec), i.e. plain, + // unformattable text such as a code block. Blocks without inline content + // but that aren't plain (e.g. images) should still show the toolbar. + let spansPlainContent = false; tr.selection.content().content.descendants((node) => { - if (node.type.spec.code) { - spansCode = true; + if (node.type.spec.content === "text*") { + spansPlainContent = true; } - return !spansCode; // keep descending if we haven't found a code block + return !spansPlainContent; // keep descending until we find plain content }); - // Don't show if the selection spans a code block. - if (spansCode) { + // Don't show if the selection spans plain content. + if (spansPlainContent) { return false; } diff --git a/packages/core/src/extensions/InlineContentBoundaryEdit/InlineContentBoundaryEdit.ts b/packages/core/src/extensions/InlineContentBoundaryEdit/InlineContentBoundaryEdit.ts new file mode 100644 index 0000000000..01c0357d03 --- /dev/null +++ b/packages/core/src/extensions/InlineContentBoundaryEdit/InlineContentBoundaryEdit.ts @@ -0,0 +1,109 @@ +import { Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state"; +import { createExtension } from "../../editor/BlockNoteExtension.js"; + +const PLUGIN_KEY = new PluginKey("inline-content-boundary-edit"); + +// Whether a Backspace/Delete at `selection` would remove the entire content +// range `[content.from, content.to)` of an inline content node. +function emptiesInlineContent( + selection: Selection, + key: string, + content: { from: number; to: number }, +) { + if (!selection.empty) { + return selection.from <= content.from && selection.to >= content.to; + } + + const isSingleChar = content.to - content.from === 1; + + return key === "Backspace" + ? isSingleChar && selection.from === content.to + : isSingleChar && selection.from === content.from; +} + +// Fixes editing at the boundary of an empty custom inline content node (i.e. an +// inline node with editable content, like a mention or inline math). +// +// An empty inline node can't hold a text cursor, so ProseMirror can't reconcile +// edits across the empty boundary from the DOM: typing into an empty node +// inserts text next to it rather than inside, and deleting the last character +// leaves an un-reconcilable empty node that corrupts/freezes the editor. Both +// boundary edits are handled here via transactions so the caret stays inside +// the node, which is kept alive and editable in its empty state. +// +// The cursor is inside such a node exactly when its directly-enclosing node is +// inline (`inline: true` in the spec) - regular text blocks aren't inline, and +// atomic inline content can't hold a cursor - so the handling applies to any +// inline content type without needing to know it by name. +export const InlineContentBoundaryEditExtension = createExtension( + () => + ({ + key: "inlineContentBoundaryEdit", + prosemirrorPlugins: [ + new Plugin({ + key: PLUGIN_KEY, + props: { + handleKeyDown: (view, event) => { + if (!view.editable) { + return false; + } + + const isTypedChar = + event.key.length === 1 && !event.ctrlKey && !event.metaKey; + + if ( + !isTypedChar && + event.key !== "Backspace" && + event.key !== "Delete" + ) { + return false; + } + + const { selection } = view.state; + const node = selection.$from.node(); + if (!node.type.spec.inline) { + return false; + } + + const pos = selection.$from.before(); + const contentFrom = pos + 1; + const contentTo = pos + 1 + node.content.size; + + // Empty content: redirect the typed character into the node. + if (isTypedChar && node.content.size === 0) { + const tr = view.state.tr.insert( + contentFrom, + view.state.schema.text(event.key), + ); + tr.setSelection( + TextSelection.create(tr.doc, contentFrom + event.key.length), + ); + view.dispatch(tr); + + return true; + } + + // Backspace/Delete that would empty the content: delete it all in + // one transaction, keeping the now-empty node (and the caret + // inside it) so it stays editable. + if ( + node.content.size > 0 && + emptiesInlineContent(selection, event.key, { + from: contentFrom, + to: contentTo, + }) + ) { + const tr = view.state.tr.delete(contentFrom, contentTo); + tr.setSelection(TextSelection.create(tr.doc, contentFrom)); + view.dispatch(tr); + + return true; + } + + return false; + }, + }, + }), + ], + }) as const, +); diff --git a/packages/core/src/extensions/SourceBlockWithPreview/SourceBlockWithPreview.ts b/packages/core/src/extensions/SourceBlockWithPreview/SourceBlockWithPreview.ts new file mode 100644 index 0000000000..c9e8648861 --- /dev/null +++ b/packages/core/src/extensions/SourceBlockWithPreview/SourceBlockWithPreview.ts @@ -0,0 +1,192 @@ +import { TextSelection } from "prosemirror-state"; + +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { + createExtension, + createStore, +} from "../../editor/BlockNoteExtension.js"; +import { Block } from "../../blocks/index.js"; + +/** + * A single editor-wide extension that drives the source popup for blocks that + * render a preview. Which blocks it activates on is decided by each spec's + * `meta.hasPreview` flag, so individual blocks opt in rather than the extension + * being configured with a block type. + * + * The extension is registered once (it's a default extension) and is a no-op + * when no block declares `meta.hasPreview`. + */ +export const SourceBlockWithPreviewExtension = createExtension( + ({ editor }: { editor: BlockNoteEditor }) => { + const store = createStore<{ + popupOpen: string | undefined; + selected: string | undefined; + }>({ + popupOpen: undefined, + selected: undefined, + }); + + // A block has a preview iff its spec's implementation declares + // `meta.hasPreview` (read from the spec, like the syntax-highlighting + // extension reads `meta.highlight`). + const blockHasPreview = (block: Block) => + !!editor.schema.blockSpecs[block.type]?.implementation?.meta?.hasPreview; + + const handleArrow = + (direction: "prev" | "next") => + ({ editor }: { editor: BlockNoteEditor }) => { + const { block, prevBlock, nextBlock } = editor.getTextCursorPosition(); + if (!blockHasPreview(block) || store.state.popupOpen === block.id) { + return false; + } + + const targetBlock = direction === "prev" ? prevBlock : nextBlock; + if (!targetBlock) { + return false; + } + + editor.setTextCursorPosition( + targetBlock.id, + direction === "prev" ? "end" : "start", + ); + + return true; + }; + + return { + key: "sourceBlockWithPreview", + store, + keyboardShortcuts: { + // Toggles the popup. This may be overridden by `hardBreakShortcut`. + Enter: ({ editor }) => { + const { block } = editor.getTextCursorPosition(); + if (!blockHasPreview(block)) { + return false; + } + + if ( + store.state.popupOpen === block.id && + editor.schema.blockSpecs[block.type]?.implementation?.meta + ?.hardBreakShortcut === "enter" + ) { + const view = editor.prosemirrorView!; + view.dispatch(view.state.tr.insertText("\n")); + + return true; + } + + editor.setTextCursorPosition(block.id, "end"); + store.setState((state) => ({ + ...state, + popupOpen: + store.state.popupOpen === block.id ? undefined : block.id, + })); + + return true; + }, + // Closes the popup. + Escape: ({ editor }) => { + const { block } = editor.getTextCursorPosition(); + if (!blockHasPreview(block) || store.state.popupOpen !== block.id) { + return false; + } + + editor.setTextCursorPosition(block.id, "end"); + + store.setState((state) => ({ ...state, popupOpen: undefined })); + + return true; + }, + // While the popup is open, selects the whole source instead of the + // whole document. + "Mod-a": ({ editor }) => { + const { block } = editor.getTextCursorPosition(); + if (!blockHasPreview(block) || store.state.popupOpen !== block.id) { + return false; + } + + const view = editor.prosemirrorView!; + const { $from } = view.state.selection; + if ($from.parent.type.name !== block.type) { + return false; + } + + view.dispatch( + view.state.tr.setSelection( + TextSelection.create(view.state.doc, $from.start(), $from.end()), + ), + ); + + return true; + }, + // While the popup is closed, moves the selection straight to the previous/next block + // instead of into the (hidden) source. + ArrowUp: handleArrow("prev"), + ArrowLeft: handleArrow("prev"), + ArrowDown: handleArrow("next"), + ArrowRight: handleArrow("next"), + }, + mount: ({ dom, signal }) => { + // Closes the popup when the selection leaves the block that owns it and tracks which block + // the selection is in. + const unsubscribeSelectionChange = editor.onSelectionChange(() => { + const { block } = editor.getTextCursorPosition(); + + const selected = blockHasPreview(block) ? block.id : undefined; + const popupOpen = + store.state.popupOpen && store.state.popupOpen !== block.id + ? undefined + : store.state.popupOpen; + + if ( + selected === store.state.selected && + popupOpen === store.state.popupOpen + ) { + return; + } + + store.setState((state) => ({ ...state, selected, popupOpen })); + }); + signal.addEventListener("abort", unsubscribeSelectionChange); + + // While the popup is closed, prevents editing of the (hidden) source. Handled here rather + // than in `keyboardShortcuts` as it needs to match any text-input key, which a keymap + // can't express. + const handleKeyDown = (event: KeyboardEvent) => { + if (!editor.isEditable) { + return; + } + + const { block } = editor.getTextCursorPosition(); + if (!blockHasPreview(block) || store.state.popupOpen === block.id) { + return; + } + + if (event.key === "Backspace" || event.key === "Delete") { + event.preventDefault(); + event.stopImmediatePropagation(); + editor.removeBlocks([block.id]); + + return; + } + + if ( + (event.key.length === 1 && !event.ctrlKey && !event.metaKey) || + event.key === "Tab" + ) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + }; + dom.addEventListener("keydown", handleKeyDown, { + capture: true, + signal, + }); + + const handleBlur = () => + store.setState((state) => ({ ...state, popupOpen: undefined })); + dom.addEventListener("blur", handleBlur, { capture: true, signal }); + }, + }; + }, +); diff --git a/packages/core/src/extensions/SourceInlineContentWithPreview/SourceInlineContentWithPreview.ts b/packages/core/src/extensions/SourceInlineContentWithPreview/SourceInlineContentWithPreview.ts new file mode 100644 index 0000000000..9ab67e93a7 --- /dev/null +++ b/packages/core/src/extensions/SourceInlineContentWithPreview/SourceInlineContentWithPreview.ts @@ -0,0 +1,142 @@ +import { Selection, TextSelection } from "prosemirror-state"; + +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { + createExtension, + createStore, +} from "../../editor/BlockNoteExtension.js"; + +/** + * Inline-content counterpart of {@link SourceBlockWithPreviewExtension}. A + * single editor-wide extension that drives the source popup for inline content + * that renders a preview. Which inline content it activates on is decided by + * each spec's `meta.hasPreview` flag, so individual inline content opts in + * rather than the extension being configured with a type. + * + * Unlike the block version, the popup isn't toggled with a separate state flag: + * it's open exactly when the selection is inside the inline content's source. + * The store therefore only tracks which inline content (by its position) holds + * the selection - moving the selection in opens its popup, moving it out closes + * it. Since the source popup is always laid out (just hidden via opacity), the + * cursor can navigate into and out of it with the arrow keys as usual. + * + * The extension is registered once (it's a default extension) and is a no-op + * when no inline content declares `meta.hasPreview`. + */ +export const SourceInlineContentWithPreviewExtension = createExtension( + ({ editor }: { editor: BlockNoteEditor }) => { + const store = createStore<{ + selected: number | undefined; + }>({ + selected: undefined, + }); + + // Inline content has a preview iff its spec's implementation declares + // `meta.hasPreview`. + const nodeHasPreview = (nodeName: string) => + !!editor.schema.inlineContentSpecs[nodeName]?.implementation?.meta + ?.hasPreview; + + // Moves the selection out of the inline content, to just `"before"` or + // `"after"` it, which closes the popup via the selection-change handler + // below. Lets the keyboard commit-and-exit the source the same way arrowing + // past its edge does, keeps Enter from splitting the block while editing the + // source, and lets the up/down arrows step out of the source rather than + // staying trapped inside it. + const moveSelectionOut = + (direction: "before" | "after") => + ({ editor }: { editor: BlockNoteEditor }) => { + const { $from } = editor.prosemirrorState.selection; + const node = $from.node(); + if (!nodeHasPreview(node.type.name)) { + return false; + } + + const view = editor.prosemirrorView!; + const selection = Selection.near( + view.state.doc.resolve( + direction === "before" ? $from.before() : $from.after(), + ), + direction === "before" ? -1 : 1, + ); + view.dispatch(view.state.tr.setSelection(selection)); + + return true; + }; + + return { + key: "sourceInlineContentWithPreview", + store, + keyboardShortcuts: { + Enter: moveSelectionOut("after"), + "Shift-Enter": moveSelectionOut("after"), + Escape: moveSelectionOut("after"), + ArrowUp: moveSelectionOut("before"), + ArrowDown: moveSelectionOut("after"), + // While editing the source, selects the whole source instead of the + // whole document. + "Mod-a": ({ editor }) => { + const { $from } = editor.prosemirrorState.selection; + if (!nodeHasPreview($from.node().type.name)) { + return false; + } + + const view = editor.prosemirrorView!; + view.dispatch( + view.state.tr.setSelection( + TextSelection.create(view.state.doc, $from.start(), $from.end()), + ), + ); + + return true; + }, + }, + mount: ({ dom, signal }) => { + // The popup is open exactly when the selection is inside the inline + // content, so we just track which inline content (if any) holds it. + const unsubscribeSelectionChange = editor.onSelectionChange(() => { + const { $from } = editor.prosemirrorState.selection; + const node = $from.node(); + + store.setState({ + selected: nodeHasPreview(node.type.name) + ? $from.before() + : undefined, + }); + }); + signal.addEventListener("abort", unsubscribeSelectionChange); + + // Sets `visibility: hidden` on the popup for a single frame when pressing up/down arrow + // keys. The popup is normally hidden through `opacity: 0`, which means it's still visible + // to the browser for navigation. Therefore, the up/down arrows can sometimes move the + // selection into the popup from unexpected positions, such as on the same line. Setting + // `visibility: hidden` makes the browser ignore it when determining the new selection. + // TODO: This is hacky, we should find a cleaner solution. + const handleVerticalArrow = (event: KeyboardEvent) => { + if (event.key !== "ArrowUp" && event.key !== "ArrowDown") { + return; + } + + // When the selection is already inside a source, leave navigation + // (moving within or out of it) to the browser as usual. + const { $from } = editor.prosemirrorState.selection; + if (nodeHasPreview($from.node().type.name)) { + return; + } + + dom.classList.add("bn-suppress-source-popup-caret"); + requestAnimationFrame(() => + dom.classList.remove("bn-suppress-source-popup-caret"), + ); + }; + dom.addEventListener("keydown", handleVerticalArrow, { + capture: true, + signal, + }); + + const handleBlur = () => store.setState({ selected: undefined }); + dom.addEventListener("blur", handleBlur, { capture: true, signal }); + }, + }; + }, +); diff --git a/packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.test.ts b/packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.test.ts new file mode 100644 index 0000000000..dd89b96ad9 --- /dev/null +++ b/packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.test.ts @@ -0,0 +1,120 @@ +import { describe, expect, it } from "vite-plus/test"; +import { + collectHighlightNodeTypes, + SyntaxHighlightingExtension, +} from "./SyntaxHighlighting.js"; + +/** + * @vitest-environment jsdom + */ + +describe("SyntaxHighlightingExtension", () => { + // The extension only reads `editor.schema.blockSpecs` and + // `inlineContentSpecs`, so a minimal stub is enough. + const fakeEditor = () => + ({ + schema: { + blockSpecs: { + paragraph: { config: { type: "paragraph", content: "inline" } }, + codeBlock: { config: { type: "codeBlock", content: "plain" } }, + image: { config: { type: "image", content: "none" } }, + }, + inlineContentSpecs: {}, + }, + }) as any; + + const pluginsFor = (options: any) => + SyntaxHighlightingExtension(options)({ editor: fakeEditor() }) + .prosemirrorPlugins; + + // Whether highlighting is enabled at all is decided by the user (they choose + // to add this extension to the editor's `extensions`), so the extension + // itself always installs the plugin once created. + it("installs a highlight plugin when a highlighter is configured", () => { + const plugins = pluginsFor({ createHighlighter: async () => ({}) as any }); + + expect(plugins).toHaveLength(1); + }); + + it("installs the plugin even without a highlighter (it no-ops at parse time)", () => { + expect(pluginsFor({})).toHaveLength(1); + }); +}); + +describe("collectHighlightNodeTypes", () => { + const highlight = () => "latex"; + + it("includes blocks with `content: plain` and a `meta.highlight`", () => { + const types = collectHighlightNodeTypes({ + blockSpecs: { + // Highlightable: plain content + a highlight callback. + mathBlock: { + config: { type: "mathBlock", content: "plain" }, + implementation: { meta: { highlight } }, + }, + // Not highlightable: no highlight callback. + paragraph: { + config: { type: "paragraph", content: "inline" }, + implementation: { meta: {} }, + }, + // Not highlightable: `content: none` holds no editable text. + image: { + config: { type: "image", content: "none" }, + implementation: { meta: { highlight } }, + }, + }, + inlineContentSpecs: {}, + }); + + expect(types).toEqual(["mathBlock"]); + }); + + it("includes inline content with `content: plain` and a `meta.highlight`", () => { + const types = collectHighlightNodeTypes({ + blockSpecs: {}, + inlineContentSpecs: { + // Highlightable: plain (editable plain text) + a highlight callback. + math: { + config: { type: "math", content: "plain" }, + implementation: { meta: { highlight } }, + }, + // Not highlightable: no highlight callback. + mention: { + config: { type: "mention", content: "plain" }, + implementation: { meta: {} }, + }, + // Not highlightable: `content: none` holds no editable text. + tag: { + config: { type: "tag", content: "none" }, + implementation: { meta: { highlight } }, + }, + // Built-in `text`/`link` specs have string configs, not objects. + text: { config: "text", implementation: undefined }, + link: { config: "link", implementation: undefined }, + }, + }); + + expect(types).toEqual(["math"]); + }); + + it("collects both block and inline-content highlight types together", () => { + const types = collectHighlightNodeTypes({ + blockSpecs: { + mathBlock: { + config: { type: "mathBlock", content: "plain" }, + implementation: { meta: { highlight } }, + }, + }, + inlineContentSpecs: { + math: { + config: { type: "math", content: "plain" }, + implementation: { meta: { highlight } }, + }, + }, + }); + + expect(types).toContain("mathBlock"); + expect(types).toContain("math"); + expect(types).toHaveLength(2); + }); +}); diff --git a/packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.ts b/packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.ts new file mode 100644 index 0000000000..877dea8428 --- /dev/null +++ b/packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.ts @@ -0,0 +1,89 @@ +import type { HighlighterGeneric } from "@shikijs/types"; +import { + createExtension, + ExtensionOptions, +} from "../../editor/BlockNoteExtension.js"; +import { lazyShikiPlugin } from "./shiki.js"; +import { + CustomInlineContentConfig, + InlineContentSpec, + LooseBlockSpec, +} from "../../schema/index.js"; + +export type SyntaxHighlightingOptions = { + /** + * Creates the Shiki highlighter used for syntax highlighting. Can be + * asynchronous, so the highlighter is only loaded once it's first needed. + * + * When omitted, content renders without syntax highlighting. + */ + createHighlighter: () => Promise>; +}; + +/** + * Collects the node type names that should be syntax-highlighted from a schema's + * block and inline-content specs. + * + * A spec is a candidate when it has a `meta.highlight` callback (which decides + * the language) AND the node actually holds editable text. Block and + * inline-content specs use different `content` value spaces, so "editable text" + * means `content === "plain"` for both blocks (code/math blocks) and inline + * content (inline math) - both hold plain text - hence the two are filtered + * separately. + * + * Inline content (e.g. inline math) is highlighted too: `prosemirror-highlight` + * collects nodes by `node.inlineContent` since v0.15.3 + * (https://github.com/ocavue/prosemirror-highlight/pull/137), so inline nodes + * holding inline content are visited alongside text blocks. + */ +export function collectHighlightNodeTypes(schema: { + blockSpecs: Record; + inlineContentSpecs: Record; +}): string[] { + const blockNodeTypes = Object.values(schema.blockSpecs) + .filter( + (blockSpec): blockSpec is LooseBlockSpec => + typeof (blockSpec as LooseBlockSpec)?.config === "object" && + (blockSpec as LooseBlockSpec).config.content === "plain" && + !!(blockSpec as LooseBlockSpec).implementation?.meta?.highlight, + ) + .map((blockSpec) => blockSpec.config.type); + + const inlineContentNodeTypes = Object.values(schema.inlineContentSpecs) + .filter( + ( + inlineContentSpec, + ): inlineContentSpec is InlineContentSpec => + typeof ( + inlineContentSpec as InlineContentSpec + )?.config === "object" && + (inlineContentSpec as InlineContentSpec) + .config.content === "plain" && + !!(inlineContentSpec as InlineContentSpec) + .implementation?.meta?.highlight, + ) + .map((inlineContentSpec) => inlineContentSpec.config.type); + + return [...blockNodeTypes, ...inlineContentNodeTypes]; +} + +/** + * A single editor-wide extension that syntax-highlights block and inline-content + * content. Which nodes get highlighted (and as which language) is decided by + * each spec's `meta.highlight` callback, so individual specs declare their own + * language rather than the extension configuring them. + * + * Highlighting is opt-in: the user adds this extension to the editor's + * `extensions` (configured with a `createHighlighter`) to enable it. When it's + * absent, content renders as plain text. + */ +export const SyntaxHighlightingExtension = createExtension( + ({ editor, options }: ExtensionOptions) => { + const nodeTypes = collectHighlightNodeTypes(editor.schema); + + return { + key: "syntaxHighlighting", + prosemirrorPlugins: [lazyShikiPlugin(options, nodeTypes, editor.schema)], + }; + }, +); diff --git a/packages/core/src/extensions/SyntaxHighlighting/inlineHighlight.test.ts b/packages/core/src/extensions/SyntaxHighlighting/inlineHighlight.test.ts new file mode 100644 index 0000000000..96e03418e7 --- /dev/null +++ b/packages/core/src/extensions/SyntaxHighlighting/inlineHighlight.test.ts @@ -0,0 +1,135 @@ +import { Schema } from "prosemirror-model"; +import { EditorState, PluginKey } from "prosemirror-state"; +import { Decoration } from "prosemirror-view"; +import { createHighlightPlugin } from "prosemirror-highlight"; +import { describe, expect, it } from "vite-plus/test"; + +/** + * @vitest-environment jsdom + */ + +// Regression coverage for inline syntax highlighting. `prosemirror-highlight` +// used to collect only text-block nodes, so inline nodes (e.g. inline math) +// were never highlighted. Since v0.15.3 it collects nodes by +// `node.inlineContent` (https://github.com/ocavue/prosemirror-highlight/pull/137), +// so inline content with a `meta.highlight` is highlighted too. This suite +// guards that behavior. It drives the plugin directly against a hand-built +// ProseMirror doc, so it needs neither Shiki nor a browser. +describe("inline syntax highlighting", () => { + // A minimal schema with an *inline* node type that holds inline content + // (`content: "text*"`), mirroring how inline content like inline math is + // structured, plus an *atom* inline node that holds no content. + const schema = new Schema({ + nodes: { + text: { group: "inline" }, + inlineCode: { + group: "inline", + inline: true, + content: "text*", + toDOM: () => ["span", 0], + }, + // An atom inline node (no inline content) - like a mention. It should + // never be collected for highlighting even if named in `nodeTypes`, since + // it holds no editable text. This is why the library keys off + // `node.inlineContent` rather than merely `node.isInline`. + mention: { + group: "inline", + inline: true, + atom: true, + toDOM: () => ["span", "@x"], + }, + paragraph: { + group: "block", + content: "inline*", + toDOM: () => ["p", 0], + }, + doc: { content: "block+" }, + }, + }); + + const docWithInline = schema.node("doc", null, [ + schema.node("paragraph", null, [ + schema.text("before "), + schema.node("inlineCode", null, [schema.text("const x = 1")]), + schema.text(" "), + schema.node("mention"), + schema.text(" after"), + ]), + ]); + + it("calls the parser with the inline node's content and decorates it", () => { + const seen: { content: string; language?: string }[] = []; + + const plugin = createHighlightPlugin({ + // Synchronous stub parser mirroring how the real Shiki parser emits token + // decorations: `Decoration.inline` over positions *inside* the node, + // starting at `pos + 1` (the node's content starts after its opening + // boundary). Node-spanning decorations aren't valid over an inline node, + // which is fine - the token decorations are what actually color the text. + parser: ({ content, language, pos }) => { + seen.push({ content, language }); + return [ + Decoration.inline(pos + 1, pos + 1 + content.length, { + class: "hl", + }), + ]; + }, + nodeTypes: ["inlineCode"], + languageExtractor: () => "javascript", + }); + + const state = EditorState.create({ + schema, + doc: docWithInline, + plugins: [plugin], + }); + + const key = (plugin as any).spec.key as PluginKey; + const pluginState = key.getState(state); + + // The inline node's text reached the parser - proving inline nodes are + // collected (with the pre-0.15.3 library, `seen` would be empty). + expect(seen).toHaveLength(1); + expect(seen[0].content).toBe("const x = 1"); + expect(seen[0].language).toBe("javascript"); + + // And a decoration was produced for it. + expect(pluginState.decorations).toBeDefined(); + expect(pluginState.decorations.find().length).toBeGreaterThan(0); + }); + + it("still leaves non-matching inline nodes untouched", () => { + const seen: string[] = []; + const plugin = createHighlightPlugin({ + parser: ({ content }) => { + seen.push(content); + return []; + }, + nodeTypes: ["somethingElse"], + languageExtractor: () => "javascript", + }); + + EditorState.create({ schema, doc: docWithInline, plugins: [plugin] }); + + expect(seen).toHaveLength(0); + }); + + it("excludes atom inline nodes even when named in `nodeTypes`", () => { + const seen: string[] = []; + const plugin = createHighlightPlugin({ + parser: ({ content }) => { + seen.push(content); + return []; + }, + // `mention` is an atom inline node with no inline content, so it holds no + // text to highlight. The library keys off `node.inlineContent`, not + // `node.isInline`, so it's correctly skipped. + nodeTypes: ["mention"], + languageExtractor: () => "javascript", + }); + + EditorState.create({ schema, doc: docWithInline, plugins: [plugin] }); + + expect(seen).toHaveLength(0); + }); +}); diff --git a/packages/core/src/extensions/SyntaxHighlighting/shiki.ts b/packages/core/src/extensions/SyntaxHighlighting/shiki.ts new file mode 100644 index 0000000000..a0e8cec88b --- /dev/null +++ b/packages/core/src/extensions/SyntaxHighlighting/shiki.ts @@ -0,0 +1,116 @@ +import type { HighlighterGeneric } from "@shikijs/types"; +import { Parser, createHighlightPlugin } from "prosemirror-highlight"; +import { createParser } from "prosemirror-highlight/shiki"; +import type { SyntaxHighlightingOptions } from "./SyntaxHighlighting.js"; +import { CustomBlockNoteSchema } from "../../schema/schema.js"; + +export const shikiParserSymbol = Symbol.for("blocknote.shikiParser"); +export const shikiHighlighterPromiseSymbol = Symbol.for( + "blocknote.shikiHighlighterPromise", +); + +// Languages that represent "no highlighting" - skipped without asking Shiki to +// load a grammar for them. +const PLAIN_TEXT_LANGUAGES = ["text", "none", "plaintext", "txt"]; + +/** + * Creates the syntax highlighting plugin for the given block types, lazily + * loading the highlighter on first use. + * + * Each spec's `meta.highlight` callback resolves a node to a language, which is + * passed straight to Shiki - it resolves aliases and loads the grammar from its + * bundle, so any language the provided highlighter bundles can be highlighted. + */ +export function lazyShikiPlugin( + options: SyntaxHighlightingOptions, + nodeTypes: string[], + schema: CustomBlockNoteSchema, +) { + const globalThisForShiki = globalThis as { + [shikiHighlighterPromiseSymbol]?: Promise>; + [shikiParserSymbol]?: Parser; + }; + + let highlighter: HighlighterGeneric | undefined; + let parser: Parser | undefined; + // Languages the highlighter failed to load (e.g. not in its bundle). Tracked + // so we don't keep retrying - and re-triggering re-highlights - forever. + const unsupportedLanguages = new Set(); + const lazyParser: Parser = (parserOptions) => { + if (!options.createHighlighter) { + return []; + } + if (!highlighter) { + globalThisForShiki[shikiHighlighterPromiseSymbol] = + globalThisForShiki[shikiHighlighterPromiseSymbol] || + options.createHighlighter(); + + return globalThisForShiki[shikiHighlighterPromiseSymbol].then( + (createdHighlighter) => { + highlighter = createdHighlighter; + }, + ); + } + const language = parserOptions.language; + + if ( + !language || + PLAIN_TEXT_LANGUAGES.includes(language) || + unsupportedLanguages.has(language) + ) { + return []; + } + + if (!highlighter.getLoadedLanguages().includes(language)) { + return highlighter.loadLanguage(language as any).catch(() => { + // The highlighter doesn't bundle this language - give up on it so we + // don't loop trying to load it on every re-highlight. + unsupportedLanguages.add(language); + }); + } + + if (!parser) { + parser = + globalThisForShiki[shikiParserSymbol] || + createParser(highlighter as any, pickThemeOptions(highlighter)); + globalThisForShiki[shikiParserSymbol] = parser; + } + + return parser(parserOptions); + }; + + return createHighlightPlugin({ + parser: lazyParser, + // The highlight plugin only gives us the block content node, so we can only + // reconstruct the block's `type` and `props` (which is all a spec's + // `meta.highlight` needs to pick a language). + languageExtractor: (node) => { + const nodeShape = { + type: node.type.name, + props: node.attrs, + }; + // search for the node in the blockSpec or inlineContentSpecs + const spec = + schema.blockSpecs[nodeShape.type] || + schema.inlineContentSpecs[nodeShape.type]; + + return spec?.implementation?.meta?.highlight?.(nodeShape) ?? undefined; + }, + nodeTypes, + }); +} + +// If a light and dark theme is added to the highlighter, this function specifies them in +// `createParser`. This lets us use `--shiki-light` and `--shiki-dark` CSS variables for correct +// styling for both light & dark editor themes. +function pickThemeOptions(highlighter: HighlighterGeneric) { + const themes = highlighter.getLoadedThemes(); + const light = themes.find((t) => /light/i.test(t)); + const dark = themes.find((t) => /dark/i.test(t)); + + if (light && dark) { + return { themes: { light, dark }, defaultColor: false as const }; + } + + return undefined; +} diff --git a/packages/core/src/extensions/Versioning/inMemoryVersioning.ts b/packages/core/src/extensions/Versioning/inMemoryVersioning.ts index 5c0491800f..75aae103d0 100644 --- a/packages/core/src/extensions/Versioning/inMemoryVersioning.ts +++ b/packages/core/src/extensions/Versioning/inMemoryVersioning.ts @@ -136,13 +136,23 @@ export function createInMemoryVersioningEndpoints(): VersioningEndpoints< const contents = new Map[]>(); let nextId = 1; + // `Date.now()` only has millisecond resolution, so two snapshots created in + // the same tick would share a timestamp and `sortSnapshotsNewestFirst` (which + // has nothing else to order on) could list them oldest-first. Hand out + // strictly increasing timestamps so creation order is always preserved. + let lastTimestamp = 0; + function nextTimestamp() { + lastTimestamp = Math.max(Date.now(), lastTimestamp + 1); + return lastTimestamp; + } + return { async list() { return sortSnapshotsNewestFirst([...snapshots]); }, async create(currentDoc, options) { - const now = Date.now(); + const now = nextTimestamp(); const id = String(nextId++); const snapshot: VersionSnapshot = { id, @@ -166,7 +176,7 @@ export function createInMemoryVersioningEndpoints(): VersioningEndpoints< // Create a "Restored from …" snapshot of the current state before // restoring, so the user can undo the restore. - const now = Date.now(); + const now = nextTimestamp(); const backupId = String(nextId++); const backup: VersionSnapshot = { id: backupId, @@ -196,7 +206,7 @@ export function createInMemoryVersioningEndpoints(): VersioningEndpoints< throw new Error(`Snapshot ${String(snapshot.id)} not found`); } stored.name = name; - stored.updatedAt = Date.now(); + stored.updatedAt = nextTimestamp(); }, async remove(snapshot) { diff --git a/packages/core/src/extensions/index.ts b/packages/core/src/extensions/index.ts index 3258f127c2..eb1d455e33 100644 --- a/packages/core/src/extensions/index.ts +++ b/packages/core/src/extensions/index.ts @@ -3,6 +3,7 @@ export * from "./DropCursor/DropCursor.js"; export * from "./FilePanel/FilePanel.js"; export * from "./FormattingToolbar/FormattingToolbar.js"; export * from "./History/History.js"; +export * from "./InlineContentBoundaryEdit/InlineContentBoundaryEdit.js"; export * from "./LinkToolbar/LinkToolbar.js"; export * from "./LinkToolbar/protocols.js"; export * from "./NodeSelectionKeyboard/NodeSelectionKeyboard.js"; @@ -11,7 +12,10 @@ export * from "./PositionMapping/PositionMapping.js"; export * from "./PreviousBlockType/PreviousBlockType.js"; export * from "./ShowSelection/ShowSelection.js"; export * from "./SideMenu/SideMenu.js"; +export * from "./SourceBlockWithPreview/SourceBlockWithPreview.js"; +export * from "./SourceInlineContentWithPreview/SourceInlineContentWithPreview.js"; export * from "./SuggestionMenu/DefaultGridSuggestionItem.js"; +export * from "./SyntaxHighlighting/SyntaxHighlighting.js"; export * from "./SuggestionMenu/DefaultSuggestionItem.js"; export * from "./SuggestionMenu/getDefaultEmojiPickerItems.js"; export * from "./SuggestionMenu/getDefaultSlashMenuItems.js"; diff --git a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.test.ts b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.test.ts index 1e27c9beeb..2f1e601a35 100644 --- a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.test.ts +++ b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.test.ts @@ -18,15 +18,17 @@ import { createBlockSpec } from "../../../schema/index.js"; const createHardBreakTestBlockSpec = < const T extends string, const S extends "shift+enter" | "enter" | "none", + const C extends "inline" | "plain", >( type: T, hardBreakShortcut: S, + content: C = "inline" as C, ) => createBlockSpec( { type, propSchema: {}, - content: "inline", + content, }, { meta: { @@ -47,11 +49,22 @@ const schema = BlockNoteSchema.create({ ...defaultBlockSpecs, hardBreakEnter: createHardBreakTestBlockSpec("hardBreakEnter", "enter"), hardBreakNone: createHardBreakTestBlockSpec("hardBreakNone", "none"), + // "plain" content (`text*`) can't hold a `hardBreak` node, so these blocks + // insert a literal newline character instead - e.g. code/math/diagram source. + hardBreakEnterPlain: createHardBreakTestBlockSpec( + "hardBreakEnterPlain", + "enter", + "plain", + ), }, }); function createEditor( - blockType: "paragraph" | "hardBreakEnter" | "hardBreakNone", + blockType: + | "paragraph" + | "hardBreakEnter" + | "hardBreakNone" + | "hardBreakEnterPlain", ) { const editor = BlockNoteEditor.create({ schema, @@ -87,6 +100,16 @@ function countHardBreaks(editor: BlockNoteEditor) { return count; } +function getTextContent(editor: BlockNoteEditor) { + let text = ""; + editor._tiptapEditor.state.doc.descendants((node) => { + if (node.isText) { + text += node.text; + } + }); + return text; +} + describe("KeyboardShortcutsExtension hardBreakShortcut", () => { it("inserts a hard break on Shift-Enter by default", () => { const editor = createEditor("paragraph"); @@ -152,4 +175,30 @@ describe("KeyboardShortcutsExtension hardBreakShortcut", () => { editor._tiptapEditor.destroy(); }); + + it('inserts a newline character on Enter when content is "plain"', () => { + const editor = createEditor("hardBreakEnterPlain"); + + pressKeys(editor, "Enter"); + + // A "plain" block can't hold a `hardBreak` node, so no node is inserted and + // the block is not split - a literal newline is added to its text instead. + expect(countHardBreaks(editor)).toBe(0); + expect(editor.document.length).toBe(1); + expect(getTextContent(editor)).toBe("Hello world\n"); + + editor._tiptapEditor.destroy(); + }); + + it('inserts a newline character on Shift-Enter when content is "plain"', () => { + const editor = createEditor("hardBreakEnterPlain"); + + pressKeys(editor, "Shift-Enter"); + + expect(countHardBreaks(editor)).toBe(0); + expect(editor.document.length).toBe(1); + expect(getTextContent(editor)).toBe("Hello world\n"); + + editor._tiptapEditor.destroy(); + }); }); diff --git a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index b3a0b62550..4d1758094a 100644 --- a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -804,9 +804,20 @@ export const KeyboardShortcutsExtension = Extension.create<{ commands.command(({ state }) => { const blockInfo = getBlockInfoFromSelection(state); + const blockSpec = + this.options.editor.schema.blockSpecs[blockInfo.blockNoteType]; + + // NOTE: This likely doesn't work as intended - `blockSchema[type]` + // holds the block *config* (type/propSchema/content), which carries + // no `meta`, so `meta?.hardBreakShortcut` is always `undefined` and + // this falls back to the default. It should read from the block + // spec's implementation instead (i.e. + // `editor.schema.blockSpecs[type].implementation.meta`), the way the + // syntax-highlighting extension reads `meta.highlight`. Left as-is + // for a follow-up pass. const blockHardBreakShortcut = - this.options.editor.schema.blockSpecs[blockInfo.blockNoteType] - ?.implementation?.meta?.hardBreakShortcut ?? "shift+enter"; + blockSpec?.implementation?.meta?.hardBreakShortcut ?? + "shift+enter"; if (blockHardBreakShortcut === "none") { return false; @@ -820,6 +831,15 @@ export const KeyboardShortcutsExtension = Extension.create<{ // both enter and shift+enter. blockHardBreakShortcut === "enter" ) { + // "plain" blocks (e.g. code/math/diagram source) hold text only + // (their content is `text*`), which can't contain a `hardBreak` + // node - inserting one would split the block into a new one. + // They represent line breaks as literal newline characters. + if (blockSpec?.config?.content === "plain") { + tr.insertText("\n", tr.selection.head); + return true; + } + const marks = tr.storedMarks || tr.selection.$head diff --git a/packages/core/src/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts index 379517c9fa..094671d920 100644 --- a/packages/core/src/i18n/locales/ar.ts +++ b/packages/core/src/i18n/locales/ar.ts @@ -185,6 +185,10 @@ export const ar: Dictionary = { toggle_blocks: { add_block_button: "تبديل فارغ. انقر لإضافة كتلة.", }, + code_block: { + add_source_button_text: "إضافة كود المصدر", + ok_button_text: "موافق", + }, // from react package: side_menu: { add_block_label: "إضافة محتوي", @@ -395,6 +399,11 @@ export const ar: Dictionary = { formatting_change_by: (formats: string, users: string) => `تغيير التنسيق (${formats}) بواسطة: ${users}`, }, + exporter: { + open_file: "فتح الملف", + open_video_file: "فتح الفيديو", + open_audio_file: "فتح الصوت", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts index 90512f2995..bf77a36a01 100644 --- a/packages/core/src/i18n/locales/de.ts +++ b/packages/core/src/i18n/locales/de.ts @@ -221,6 +221,10 @@ export const de: Dictionary = { add_block_button: "Leerer aufklappbarer Bereich. Klicken, um einen Block hinzuzufügen.", }, + code_block: { + add_source_button_text: "Quellcode hinzufügen", + ok_button_text: "OK", + }, side_menu: { add_block_label: "Block hinzufügen", drag_handle_label: "Blockmenü öffnen", @@ -429,6 +433,11 @@ export const de: Dictionary = { formatting_change_by: (formats: string, users: string) => `Formatierungsänderung (${formats}) von: ${users}`, }, + exporter: { + open_file: "Datei öffnen", + open_video_file: "Video öffnen", + open_audio_file: "Audio öffnen", + }, generic: { ctrl_shortcut: "Strg", }, diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index 6493c0f77b..e5386f3020 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -200,6 +200,10 @@ export const en = { toggle_blocks: { add_block_button: "Empty toggle. Click to add a block.", }, + code_block: { + add_source_button_text: "Add source code", + ok_button_text: "OK", + }, // from react package: side_menu: { add_block_label: "Add block", @@ -410,6 +414,11 @@ export const en = { formatting_change_by: (formats: string, users: string) => `Formatting change (${formats}) by: ${users}`, }, + exporter: { + open_file: "Open file", + open_video_file: "Open video", + open_audio_file: "Open audio", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/es.ts b/packages/core/src/i18n/locales/es.ts index 1b7f6bc240..743a1be05c 100644 --- a/packages/core/src/i18n/locales/es.ts +++ b/packages/core/src/i18n/locales/es.ts @@ -200,6 +200,10 @@ export const es: Dictionary = { toggle_blocks: { add_block_button: "Toggle vacío. Haz clic para añadir un bloque.", }, + code_block: { + add_source_button_text: "Agregar código fuente", + ok_button_text: "Aceptar", + }, side_menu: { add_block_label: "Agregar bloque", drag_handle_label: "Abrir menú de bloque", @@ -408,6 +412,11 @@ export const es: Dictionary = { formatting_change_by: (formats: string, users: string) => `Cambio de formato (${formats}) por: ${users}`, }, + exporter: { + open_file: "Abrir archivo", + open_video_file: "Abrir vídeo", + open_audio_file: "Abrir audio", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/fa.ts b/packages/core/src/i18n/locales/fa.ts index c62f9ad72e..6b2783ab68 100644 --- a/packages/core/src/i18n/locales/fa.ts +++ b/packages/core/src/i18n/locales/fa.ts @@ -168,6 +168,10 @@ export const fa = { toggle_blocks: { add_block_button: "تاشوی خالی. برای افزودن بلوک کلیک کنید.", }, + code_block: { + add_source_button_text: "افزودن کد منبع", + ok_button_text: "تأیید", + }, // from react package: side_menu: { add_block_label: "افزودن بلوک", @@ -379,6 +383,11 @@ export const fa = { formatting_change_by: (formats: string, users: string) => `تغییر قالب‌بندی (${formats}) توسط: ${users}`, }, + exporter: { + open_file: "باز کردن فایل", + open_video_file: "باز کردن ویدیو", + open_audio_file: "باز کردن صدا", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts index 9386d85e85..ad605db24a 100644 --- a/packages/core/src/i18n/locales/fr.ts +++ b/packages/core/src/i18n/locales/fr.ts @@ -246,6 +246,10 @@ export const fr: Dictionary = { toggle_blocks: { add_block_button: "Liste repliable vide. Cliquez pour ajouter un bloc.", }, + code_block: { + add_source_button_text: "Ajouter le code source", + ok_button_text: "OK", + }, // from react package: side_menu: { add_block_label: "Ajouter un bloc", @@ -456,6 +460,11 @@ export const fr: Dictionary = { formatting_change_by: (formats: string, users: string) => `Modification de mise en forme (${formats}) par : ${users}`, }, + exporter: { + open_file: "Ouvrir le fichier", + open_video_file: "Ouvrir la vidéo", + open_audio_file: "Ouvrir l'audio", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/he.ts b/packages/core/src/i18n/locales/he.ts index 33f866fcaa..4662a94202 100644 --- a/packages/core/src/i18n/locales/he.ts +++ b/packages/core/src/i18n/locales/he.ts @@ -202,6 +202,10 @@ export const he: Dictionary = { toggle_blocks: { add_block_button: "מתג ריק. לחץ כדי להוסיף בלוק.", }, + code_block: { + add_source_button_text: "הוסף קוד מקור", + ok_button_text: "אישור", + }, side_menu: { add_block_label: "הוסף בלוק", drag_handle_label: "פתח תפריט בלוק", @@ -410,6 +414,11 @@ export const he: Dictionary = { formatting_change_by: (formats: string, users: string) => `שינוי עיצוב (${formats}) על ידי: ${users}`, }, + exporter: { + open_file: "פתח קובץ", + open_video_file: "פתח וידאו", + open_audio_file: "פתח שמע", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/hr.ts b/packages/core/src/i18n/locales/hr.ts index 56fcd64d97..03eb016eed 100644 --- a/packages/core/src/i18n/locales/hr.ts +++ b/packages/core/src/i18n/locales/hr.ts @@ -213,6 +213,10 @@ export const hr: Dictionary = { toggle_blocks: { add_block_button: "Prazan sklopivi blok. Klikni da dodaš sadržaj.", }, + code_block: { + add_source_button_text: "Dodaj izvorni kôd", + ok_button_text: "U redu", + }, // from react package: side_menu: { add_block_label: "Dodaj blok", @@ -424,6 +428,11 @@ export const hr: Dictionary = { formatting_change_by: (formats: string, users: string) => `Promjena oblikovanja (${formats}) od: ${users}`, }, + exporter: { + open_file: "Otvori datoteku", + open_video_file: "Otvori videozapis", + open_audio_file: "Otvori audiozapis", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts index 32ba20614d..913b2324b0 100644 --- a/packages/core/src/i18n/locales/is.ts +++ b/packages/core/src/i18n/locales/is.ts @@ -214,6 +214,10 @@ export const is: Dictionary = { toggle_blocks: { add_block_button: "Tóm fellilína. Smelltu til að bæta við blokk.", }, + code_block: { + add_source_button_text: "Bæta við frumkóða", + ok_button_text: "Í lagi", + }, side_menu: { add_block_label: "Bæta við blokki", drag_handle_label: "Opna blokkarvalmynd", @@ -424,6 +428,11 @@ export const is: Dictionary = { formatting_change_by: (formats: string, users: string) => `Sniðbreyting (${formats}) af: ${users}`, }, + exporter: { + open_file: "Opna skrá", + open_video_file: "Opna myndband", + open_audio_file: "Opna hljóð", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/it.ts b/packages/core/src/i18n/locales/it.ts index bbb144d33b..44be22c1bd 100644 --- a/packages/core/src/i18n/locales/it.ts +++ b/packages/core/src/i18n/locales/it.ts @@ -222,6 +222,10 @@ export const it: Dictionary = { toggle_blocks: { add_block_button: "Toggle vuoto. Clicca per aggiungere un blocco.", }, + code_block: { + add_source_button_text: "Aggiungi codice sorgente", + ok_button_text: "OK", + }, // from react package: side_menu: { add_block_label: "Aggiungi blocco", @@ -432,6 +436,11 @@ export const it: Dictionary = { formatting_change_by: (formats: string, users: string) => `Modifica formattazione (${formats}) da: ${users}`, }, + exporter: { + open_file: "Apri file", + open_video_file: "Apri video", + open_audio_file: "Apri audio", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts index 792bc9f363..ead1f2fb30 100644 --- a/packages/core/src/i18n/locales/ja.ts +++ b/packages/core/src/i18n/locales/ja.ts @@ -240,6 +240,10 @@ export const ja: Dictionary = { toggle_blocks: { add_block_button: "空のトグルです。クリックしてブロックを追加。", }, + code_block: { + add_source_button_text: "ソースコードを追加", + ok_button_text: "OK", + }, // from react package: side_menu: { add_block_label: "ブロックを追加", @@ -450,6 +454,11 @@ export const ja: Dictionary = { formatting_change_by: (formats: string, users: string) => `書式の変更 (${formats}) 変更者: ${users}`, }, + exporter: { + open_file: "ファイルを開く", + open_video_file: "動画を開く", + open_audio_file: "音声を開く", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts index a2e341e976..2981ff1c36 100644 --- a/packages/core/src/i18n/locales/ko.ts +++ b/packages/core/src/i18n/locales/ko.ts @@ -213,6 +213,10 @@ export const ko: Dictionary = { toggle_blocks: { add_block_button: "비어 있는 토글입니다. 클릭하여 블록을 추가하세요.", }, + code_block: { + add_source_button_text: "소스 코드 추가", + ok_button_text: "확인", + }, // from react package: side_menu: { add_block_label: "블록 추가", @@ -423,6 +427,11 @@ export const ko: Dictionary = { formatting_change_by: (formats: string, users: string) => `서식 변경 (${formats}) 변경한 사람: ${users}`, }, + exporter: { + open_file: "파일 열기", + open_video_file: "동영상 열기", + open_audio_file: "오디오 열기", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index c258000eb1..da599e017c 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -201,6 +201,10 @@ export const nl: Dictionary = { toggle_blocks: { add_block_button: "Lege uitklapper. Klik om een blok toe te voegen.", }, + code_block: { + add_source_button_text: "Broncode toevoegen", + ok_button_text: "OK", + }, // from react package: side_menu: { add_block_label: "Nieuw blok", @@ -411,6 +415,11 @@ export const nl: Dictionary = { formatting_change_by: (formats: string, users: string) => `Opmaakwijziging (${formats}) door: ${users}`, }, + exporter: { + open_file: "Bestand openen", + open_video_file: "Video openen", + open_audio_file: "Audio openen", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/no.ts b/packages/core/src/i18n/locales/no.ts index bfd4a4ac74..72efc096ed 100644 --- a/packages/core/src/i18n/locales/no.ts +++ b/packages/core/src/i18n/locales/no.ts @@ -219,6 +219,10 @@ export const no: Dictionary = { toggle_blocks: { add_block_button: "Tomt toggle. Klikk for å legge til en blokk.", }, + code_block: { + add_source_button_text: "Legg til kildekode", + ok_button_text: "OK", + }, side_menu: { add_block_label: "Legg til blokk", drag_handle_label: "Åpne blokkmeny", @@ -428,6 +432,11 @@ export const no: Dictionary = { formatting_change_by: (formats: string, users: string) => `Formateringsendring (${formats}) av: ${users}`, }, + exporter: { + open_file: "Åpne fil", + open_video_file: "Åpne video", + open_audio_file: "Åpne lyd", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts index fe928d4477..d00039633c 100644 --- a/packages/core/src/i18n/locales/pl.ts +++ b/packages/core/src/i18n/locales/pl.ts @@ -192,6 +192,10 @@ export const pl: Dictionary = { add_block_button: "Brak bloków do rozwinięcia. Kliknij, aby dodać pierwszego.", }, + code_block: { + add_source_button_text: "Dodaj kod źródłowy", + ok_button_text: "OK", + }, side_menu: { add_block_label: "Dodaj blok", drag_handle_label: "Otwórz menu bloków", @@ -401,6 +405,11 @@ export const pl: Dictionary = { formatting_change_by: (formats: string, users: string) => `Zmiana formatowania (${formats}) przez: ${users}`, }, + exporter: { + open_file: "Otwórz plik", + open_video_file: "Otwórz wideo", + open_audio_file: "Otwórz audio", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index 7598f092c2..fe719ce023 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -192,6 +192,10 @@ export const pt: Dictionary = { toggle_blocks: { add_block_button: "Toggle vazio. Clique para adicionar um bloco.", }, + code_block: { + add_source_button_text: "Adicionar código-fonte", + ok_button_text: "OK", + }, // from react package: side_menu: { add_block_label: "Adicionar bloco", @@ -403,6 +407,11 @@ export const pt: Dictionary = { formatting_change_by: (formats: string, users: string) => `Alteração de formatação (${formats}) por: ${users}`, }, + exporter: { + open_file: "Abrir arquivo", + open_video_file: "Abrir vídeo", + open_audio_file: "Abrir áudio", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts index 3546f4fa80..a4a7987dfc 100644 --- a/packages/core/src/i18n/locales/ru.ts +++ b/packages/core/src/i18n/locales/ru.ts @@ -243,6 +243,10 @@ export const ru: Dictionary = { toggle_blocks: { add_block_button: "Пустой переключатель. Нажмите, чтобы добавить блок.", }, + code_block: { + add_source_button_text: "Добавить исходный код", + ok_button_text: "ОК", + }, // from react package: side_menu: { add_block_label: "Добавить блок", @@ -454,6 +458,11 @@ export const ru: Dictionary = { formatting_change_by: (formats: string, users: string) => `Изменение форматирования (${formats}): ${users}`, }, + exporter: { + open_file: "Открыть файл", + open_video_file: "Открыть видео", + open_audio_file: "Открыть аудио", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/sk.ts b/packages/core/src/i18n/locales/sk.ts index e70d44d6e9..4e73dc7eca 100644 --- a/packages/core/src/i18n/locales/sk.ts +++ b/packages/core/src/i18n/locales/sk.ts @@ -200,6 +200,10 @@ export const sk = { toggle_blocks: { add_block_button: "Prázdne prepínanie. Kliknite pre pridanie bloku.", }, + code_block: { + add_source_button_text: "Pridať zdrojový kód", + ok_button_text: "OK", + }, side_menu: { add_block_label: "Pridať blok", drag_handle_label: "Otvoriť menu bloku", @@ -408,6 +412,11 @@ export const sk = { formatting_change_by: (formats: string, users: string) => `Zmena formátovania (${formats}) od: ${users}`, }, + exporter: { + open_file: "Otvoriť súbor", + open_video_file: "Otvoriť video", + open_audio_file: "Otvoriť zvuk", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/uk.ts b/packages/core/src/i18n/locales/uk.ts index 68cbc2af4b..e9d379ac0b 100644 --- a/packages/core/src/i18n/locales/uk.ts +++ b/packages/core/src/i18n/locales/uk.ts @@ -225,6 +225,10 @@ export const uk: Dictionary = { toggle_blocks: { add_block_button: "Порожній перемикач. Натисніть, щоб додати блок.", }, + code_block: { + add_source_button_text: "Додати вихідний код", + ok_button_text: "ОК", + }, // from react package: side_menu: { add_block_label: "Додати блок", @@ -434,6 +438,11 @@ export const uk: Dictionary = { formatting_change_by: (formats: string, users: string) => `Зміна форматування (${formats}) користувачем: ${users}`, }, + exporter: { + open_file: "Відкрити файл", + open_video_file: "Відкрити відео", + open_audio_file: "Відкрити аудіо", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/uz.ts b/packages/core/src/i18n/locales/uz.ts index 618d861654..13aee55a73 100644 --- a/packages/core/src/i18n/locales/uz.ts +++ b/packages/core/src/i18n/locales/uz.ts @@ -262,6 +262,10 @@ export const uz: Dictionary = { add_block_button: "Bo‘sh toggle. Blok qo‘shish uchun bosing.", }, + code_block: { + add_source_button_text: "Manba kodini qoʻshish", + ok_button_text: "OK", + }, side_menu: { add_block_label: "Blok qo‘shish", drag_handle_label: "Blok menyusini ochish", @@ -444,6 +448,11 @@ export const uz: Dictionary = { formatting_change_by: (formats: string, users: string) => `Formatlash o'zgarishi (${formats}), o'zgartirgan: ${users}`, }, + exporter: { + open_file: "Faylni ochish", + open_video_file: "Videoni ochish", + open_audio_file: "Audioni ochish", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts index 88ee9794fe..8733fbf0ba 100644 --- a/packages/core/src/i18n/locales/vi.ts +++ b/packages/core/src/i18n/locales/vi.ts @@ -199,6 +199,10 @@ export const vi: Dictionary = { toggle_blocks: { add_block_button: "Toggle trống. Nhấp để thêm khối.", }, + code_block: { + add_source_button_text: "Thêm mã nguồn", + ok_button_text: "OK", + }, // từ gói phản ứng: side_menu: { add_block_label: "Thêm khối", @@ -409,6 +413,11 @@ export const vi: Dictionary = { formatting_change_by: (formats: string, users: string) => `Thay đổi định dạng (${formats}) bởi: ${users}`, }, + exporter: { + open_file: "Mở tệp", + open_video_file: "Mở video", + open_audio_file: "Mở âm thanh", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/zh-tw.ts b/packages/core/src/i18n/locales/zh-tw.ts index 1ed96794e0..5ac37a80c7 100644 --- a/packages/core/src/i18n/locales/zh-tw.ts +++ b/packages/core/src/i18n/locales/zh-tw.ts @@ -241,6 +241,10 @@ export const zhTW: Dictionary = { toggle_blocks: { add_block_button: "空的切換區。點擊新增區塊。", }, + code_block: { + add_source_button_text: "新增原始碼", + ok_button_text: "確定", + }, // from react package: side_menu: { add_block_label: "新增區塊", @@ -451,6 +455,11 @@ export const zhTW: Dictionary = { formatting_change_by: (formats: string, users: string) => `格式變更(${formats}),變更者:${users}`, }, + exporter: { + open_file: "開啟檔案", + open_video_file: "開啟影片", + open_audio_file: "開啟音訊", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts index f5f4a5fb6a..3f4c90bb56 100644 --- a/packages/core/src/i18n/locales/zh.ts +++ b/packages/core/src/i18n/locales/zh.ts @@ -241,6 +241,10 @@ export const zh: Dictionary = { toggle_blocks: { add_block_button: "空的切换区。点击添加区块。", }, + code_block: { + add_source_button_text: "添加源代码", + ok_button_text: "确定", + }, // from react package: side_menu: { add_block_label: "添加块", @@ -451,6 +455,11 @@ export const zh: Dictionary = { formatting_change_by: (formats: string, users: string) => `格式更改(${formats}),更改者:${users}`, }, + exporter: { + open_file: "打开文件", + open_video_file: "打开视频", + open_audio_file: "打开音频", + }, generic: { ctrl_shortcut: "Ctrl", }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8c9b6066b9..b4f220e1e2 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -28,7 +28,10 @@ export * from "./util/string.js"; export * from "./util/table.js"; export * from "./util/typescript.js"; -export type { CodeBlockOptions } from "./blocks/Code/block.js"; +export type { + CodeBlockOptions, + CodeBlockPreview, +} from "./blocks/Code/CodeBlockOptions.js"; export { assertEmpty, UnreachableCaseError } from "./util/typescript.js"; export * from "./util/EventEmitter.js"; diff --git a/packages/core/src/schema/blocks/createSpec.ts b/packages/core/src/schema/blocks/createSpec.ts index 5db7ff48eb..b1e54d640a 100644 --- a/packages/core/src/schema/blocks/createSpec.ts +++ b/packages/core/src/schema/blocks/createSpec.ts @@ -7,12 +7,12 @@ import { } from "@tiptap/pm/model"; import { NodeView } from "@tiptap/pm/view"; import { mergeParagraphs } from "../../blocks/defaultBlockHelpers.js"; -import { ignoreNonContentMutations } from "../nodeViewMutations.js"; import { Extension, ExtensionFactoryInstance, } from "../../editor/BlockNoteExtension.js"; import { nonFormattingMarks } from "../markGroups.js"; +import { ignoreNonContentMutations } from "../nodeViewMutations.js"; import { PropSchema } from "../propTypes.js"; import { getBlockFromNodeView, @@ -286,8 +286,7 @@ export function addNodeAndExtensionsToSpec< // See explanation for why `update` is not implemented for NodeViews // https://github.com/TypeCellOS/BlockNote/pull/1904#discussion_r2313461464 - // TODO: in a future version, we might want to implement updates so that - // vanilla blocks don't always re-render entirely (https://github.com/TypeCellOS/BlockNote/issues/220) + // https://github.com/TypeCellOS/BlockNote/issues/220 return typedNodeView; }; }, diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 00b564ef0b..8d7e203e61 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -1,7 +1,11 @@ /** Define the main block types **/ // import { Extension, Node } from "@tiptap/core"; import type { Node, NodeViewRendererProps } from "@tiptap/core"; -import type { Fragment, Schema } from "prosemirror-model"; +import type { + Fragment, + Node as ProsemirrorNode, + Schema, +} from "prosemirror-model"; import type { ViewMutationRecord } from "prosemirror-view"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import type { @@ -28,7 +32,10 @@ export type BlockNoteDOMAttributes = Partial<{ [DOMElement in BlockNoteDOMElement]: Record; }>; -export interface BlockConfigMeta { +export interface BlockConfigMeta< + TName extends string = string, + TProps extends PropSchema = PropSchema, +> { /** * Defines which keyboard shortcut should be used to insert a hard break into the block's inline content. * @default "shift+enter" @@ -59,6 +66,18 @@ export interface BlockConfigMeta { * Whether the block is a {@link https://prosemirror.net/docs/ref/#model.NodeSpec.isolating} block */ isolating?: boolean; + + /** + * Enables syntax highlighting of the contents of the block with the result of this callback + */ + highlight?(block: { type: TName; props: Props }): string | undefined; + + /** + * Marks the block as rendering a preview with an editable source popup, driven + * by the editor-wide `SourceBlockWithPreviewExtension`. When `true`, the + * block's source is hidden behind its preview and edited via the popup. + */ + hasPreview?: boolean; } /** @@ -209,6 +228,7 @@ export type LooseBlockSpec< dom: HTMLElement | DocumentFragment; contentDOM?: HTMLElement; ignoreMutation?: (mutation: ViewMutationRecord) => boolean; + update?: (node: ProsemirrorNode) => boolean; destroy?: () => void; }; toExternalHTML?: ( @@ -267,6 +287,7 @@ export type BlockSpecs = { dom: HTMLElement | DocumentFragment; contentDOM?: HTMLElement; ignoreMutation?: (mutation: ViewMutationRecord) => boolean; + update?: (node: ProsemirrorNode) => boolean; destroy?: () => void; }; toExternalHTML?: ( @@ -354,6 +375,24 @@ export type PartialPlainContent = | string | (string | (StyledText<{}> & { styles: Record }))[]; +/** + * The text of a block's `"plain"` content (e.g. a code block's source code). + * Accepts the partial form too: block render/export paths can receive + * `PartialBlock`s (e.g. the HTML serializers take them directly), where + * plain content may still be the bare-string sugar. + */ +export function plainContentToString( + content: PlainContent | PartialPlainContent, +): string { + if (typeof content === "string") { + return content; + } + + return content + .map((item) => (typeof item === "string" ? item : item.text)) + .join(""); +} + // A BlockConfig has all the information to get the type of a Block (which is a specific instance of the BlockConfig. // i.e.: paragraphConfig: BlockConfig defines what a "paragraph" is / supports, and BlockFromConfigNoChildren is the shape of a specific paragraph block. // (for internal use) @@ -518,7 +557,7 @@ export type BlockImplementation< /** * Metadata */ - meta?: BlockConfigMeta; + meta?: BlockConfigMeta; /** * A function that converts the block into a DOM element */ @@ -552,6 +591,17 @@ export type BlockImplementation< dom: HTMLElement | DocumentFragment; contentDOM?: HTMLElement; ignoreMutation?: (mutation: ViewMutationRecord) => boolean; + /** + * Called by ProseMirror when this block's node is updated (e.g. its content + * or props change). Return `true` to handle the update in place - keeping + * the existing DOM - or `false` to have the node view recreated via + * `render`. When omitted, ProseMirror keeps the node view and reconciles its + * `contentDOM` in place as long as the node type stays the same. + * + * Useful for blocks whose `render` builds custom DOM that needs to stay in + * sync with the node (e.g. a code block rendering a preview of its content). + */ + update?: (node: ProsemirrorNode) => boolean; destroy?: () => void; }; diff --git a/packages/core/src/schema/contentTypePropagation.test.ts b/packages/core/src/schema/contentTypePropagation.test.ts index a22b129ad6..e17f9209c1 100644 --- a/packages/core/src/schema/contentTypePropagation.test.ts +++ b/packages/core/src/schema/contentTypePropagation.test.ts @@ -24,7 +24,7 @@ import type { StyleSchema } from "./styles/types.js"; * `undefined`. * * The assertions are the type annotations and `@ts-expect-error` directives: if - * propagation breaks, this file stops compiling, which `vp lint` / `tsgo` + * propagation breaks, this file stops compiling, which `vp lint` / `tsc` * catches in CI (note that `vp test`, which strips types via esbuild, does not * — these tests are guarded by the type-checker, not the runner). The `it` * bodies otherwise contain no meaningful runtime logic, mirroring the existing diff --git a/packages/core/src/schema/inlineContent/createSpec.test.ts b/packages/core/src/schema/inlineContent/createSpec.test.ts new file mode 100644 index 0000000000..b0c1ed5639 --- /dev/null +++ b/packages/core/src/schema/inlineContent/createSpec.test.ts @@ -0,0 +1,161 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { BlockNoteSchema } from "../../blocks/BlockNoteSchema.js"; +import { + defaultBlockSpecs, + defaultInlineContentSpecs, +} from "../../blocks/defaultBlocks.js"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import { YAttributionMarksExtension } from "../../y/extensions/YAttributionMarks.js"; +import { createInlineContentSpec } from "./createSpec.js"; + +// A minimal "plain" inline content, matched from external HTML by a `parse` +// function (the `tag: "*"` parse rule). +const customPlainIC = createInlineContentSpec( + { + type: "customPlainIC", + propSchema: {}, + content: "plain", + }, + { + parse: (el) => (el.classList?.contains("custom-plain-ic") ? {} : undefined), + render: () => { + const dom = document.createElement("span"); + const contentDOM = document.createElement("span"); + dom.append(contentDOM); + return { dom, contentDOM }; + }, + }, +); + +const createEditor = () => + BlockNoteEditor.create({ + schema: BlockNoteSchema.create({ + blockSpecs: defaultBlockSpecs, + inlineContentSpecs: { + ...defaultInlineContentSpecs, + customPlainIC, + }, + }), + }); + +describe("plain inline content", () => { + it("holds its text as a string in the block model", () => { + const editor = createEditor(); + + editor.replaceBlocks(editor.document, [ + { + type: "paragraph", + content: [ + { + type: "customPlainIC", + props: {}, + content: "hello world", + } as any, + ], + }, + ]); + + const inlineContent = (editor.document[0] as any).content[0]; + expect(inlineContent.type).toBe("customPlainIC"); + expect(inlineContent.content).toBe("hello world"); + + editor._tiptapEditor.destroy(); + }); + + it("round-trips a newline in its string content", () => { + const editor = createEditor(); + + // Plain content holds raw text, so newlines are kept as characters rather + // than being split into (disallowed) `hardBreak` nodes. + editor.replaceBlocks(editor.document, [ + { + type: "paragraph", + content: [ + { + type: "customPlainIC", + props: {}, + content: "first\nsecond", + } as any, + ], + }, + ]); + + const inlineContent = (editor.document[0] as any).content[0]; + expect(inlineContent.type).toBe("customPlainIC"); + expect(inlineContent.content).toBe("first\nsecond"); + + editor._tiptapEditor.destroy(); + }); + + it("keeps text and drops formatting marks when parsed", () => { + const editor = createEditor(); + + const blocks = editor.tryParseHTMLToBlocks( + `

hello world

`, + ); + + const inlineContent = (blocks[0] as any).content[0]; + expect(inlineContent.type).toBe("customPlainIC"); + expect(inlineContent.content).toBe("hello world"); + + editor._tiptapEditor.destroy(); + }); + + it("converts line breaks to newline characters when parsed", () => { + const editor = createEditor(); + + const blocks = editor.tryParseHTMLToBlocks( + `

first
second

`, + ); + + const inlineContent = (blocks[0] as any).content[0]; + expect(inlineContent.type).toBe("customPlainIC"); + expect(inlineContent.content).toBe("first\nsecond"); + + editor._tiptapEditor.destroy(); + }); + + it("disallows formatting marks but allows annotation marks", () => { + // Checked at the ProseMirror level because the block model intentionally + // represents plain content as a bare string, without marks. + // + // Every non-formatting mark comes from an optional extension. The Yjs + // attribution marks are the ones reachable from core, so they stand in for + // the `"annotation"` group here; register them so the group is non-empty + // (mirroring the "plain" block test). + const editor = BlockNoteEditor.create({ + schema: BlockNoteSchema.create({ + blockSpecs: defaultBlockSpecs, + inlineContentSpecs: { + ...defaultInlineContentSpecs, + customPlainIC, + }, + }), + extensions: [YAttributionMarksExtension()], + }); + + const plainType = editor.pmSchema.nodes["customPlainIC"]; + const insertMark = editor.pmSchema.marks["y-attributed-insert"]; + const boldMark = editor.pmSchema.marks["bold"]; + + // The plain inline content allows the non-formatting mark but not the + // formatting one. + expect(plainType.allowsMarkType(insertMark)).toBe(true); + expect(plainType.allowsMarkType(boldMark)).toBe(false); + + // ...and so `allowedMarks` keeps the former while dropping the latter. + const markNames = new Set( + plainType + .allowedMarks([ + insertMark.create({ userIds: ["test-user"] }), + boldMark.create(), + ]) + .map((m) => m.type.name), + ); + expect(markNames.has("y-attributed-insert")).toBe(true); + expect(markNames.has("bold")).toBe(false); + + editor._tiptapEditor.destroy(); + }); +}); diff --git a/packages/core/src/schema/inlineContent/createSpec.ts b/packages/core/src/schema/inlineContent/createSpec.ts index 7b6505d245..103dec52a8 100644 --- a/packages/core/src/schema/inlineContent/createSpec.ts +++ b/packages/core/src/schema/inlineContent/createSpec.ts @@ -1,11 +1,18 @@ import { Node } from "@tiptap/core"; -import { TagParseRule } from "@tiptap/pm/model"; +import { + DOMParser, + Fragment, + Node as ProsemirrorNode, + Schema, + TagParseRule, +} from "@tiptap/pm/model"; import { inlineContentToNodes } from "../../api/nodeConversions/blockToNode.js"; import { nodeToCustomInlineContent } from "../../api/nodeConversions/nodeToBlock.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { ignoreNonContentMutations } from "../nodeViewMutations.js"; import { propsToAttributes } from "../blocks/internal.js"; +import { nonFormattingMarks } from "../markGroups.js"; import { Props } from "../propTypes.js"; import { StyleSchema } from "../styles/types.js"; import { @@ -26,6 +33,23 @@ export type CustomInlineContentImplementation< > = { meta?: { draggable?: boolean; + code?: boolean; + /** + * When {@link code} is `true`, this can syntax highlight the contents of the + * inline content with the result of this callback. + */ + // Method syntax (rather than an arrow-function property) so its parameter is + // checked bivariantly, keeping a specific implementation assignable to the + // generic spec record type. + highlight?( + inlineContent: Pick, "type" | "props">, + ): string | undefined; + /** + * Marks the inline content as rendering a preview with an editable source + * popup, driven by the editor-wide + * `SourceInlineContentWithPreviewExtension`. + */ + hasPreview?: boolean; }; /** @@ -33,6 +57,17 @@ export type CustomInlineContentImplementation< */ parse?: (el: HTMLElement) => Partial> | undefined; + /** + * Advanced parsing function that controls how the content within the inline + * content is parsed. This is not recommended to use, and is only useful for + * advanced use cases. Only applies to inline content with `content: "styled"`. + * Return `undefined` to fall through to the default inline content parsing. + */ + parseContent?: (options: { + el: HTMLElement; + schema: Schema; + }) => Fragment | undefined; + /** * Renders an inline content to DOM elements */ @@ -55,6 +90,16 @@ export type CustomInlineContentImplementation< editor: BlockNoteEditor, // (note) if we want to fix the manual cast, we need to prevent circular references and separate block definition and render implementations // or allow manually passing , but that's not possible without passing the other generics because Typescript doesn't support partial inferred generics + /** + * The ProseMirror node backing this inline content. + */ + node: ProsemirrorNode, + /** + * Returns this inline content's position in the document. When rendered + * outside the editor (i.e. serialized to HTML), this is a no-op that returns + * `undefined`. + */ + getPos: () => number | undefined, ) => { dom: HTMLElement; contentDOM?: HTMLElement; @@ -86,22 +131,98 @@ export type CustomInlineContentImplementation< runsBefore?: string[]; }; +// Resolves the element whose children hold the inline content's editable +// content, i.e. the `[data-editable]` element (or the element itself if it is / +// contains none). +function getEditableElement(element: HTMLElement) { + if (element.matches("[data-editable]")) { + return element; + } + + return element.querySelector("[data-editable]") || element; +} + +// Parses an element's children as inline content. +function parseInlineContent(el: HTMLElement, schema: Schema) { + return DOMParser.fromSchema(schema).parse(el, { + topNode: schema.nodes.paragraph.create(), + preserveWhitespace: true, + }).content; +} + +// Flattens parsed inline content into text nodes only. "plain" inline content +// holds text only, so non-text inline nodes are flattened: line breaks become +// newline characters and other nodes (e.g. mentions) are kept as their text. +function flattenToText(content: Fragment, schema: Schema) { + const textNodes: ProsemirrorNode[] = []; + content.forEach((child) => { + if (child.isText) { + textNodes.push(child); + } else { + const text = + child.type === schema.linebreakReplacement ? "\n" : child.textContent; + if (text) { + textNodes.push(schema.text(text, child.marks)); + } + } + }); + + return Fragment.fromArray(textNodes); +} + export function getInlineContentParseRules( config: C, customParseFunction?: CustomInlineContentImplementation["parse"], + customParseContentFunction?: CustomInlineContentImplementation< + C, + any + >["parseContent"], ) { + // When a custom `parseContent` function is provided (and this inline content + // actually holds content), it controls how content within the inline content + // is parsed. This applies to _both_ parse rules below, as content copied from + // within the editor is tagged with `data-inline-content-type` (matched by the + // first rule), while content pasted from outside is matched by the custom + // `parse` function (the second rule). `resolveContentElement` locates the + // element whose children to parse as a fallback when `parseContent` returns + // `undefined`. + // "plain" inline content always needs `getContent` so its parsed content is + // flattened to text (`
`/line breaks become newline characters), regardless + // of whether a custom `parseContent` is provided — mirroring "plain" blocks. + // "styled" inline content only needs it to run a custom `parseContent`. + const getContent = + config.content === "plain" || + (customParseContentFunction && config.content === "styled") + ? (resolveContentElement: (el: HTMLElement) => HTMLElement) => + (node: HTMLElement, schema: Schema) => { + const result = customParseContentFunction?.({ el: node, schema }); + + // `parseContent` may return `undefined` to fall through to the + // default inline content parsing. + if (result !== undefined) { + return config.content === "plain" + ? flattenToText(result, schema) + : result; + } + + const parsed = parseInlineContent( + resolveContentElement(node), + schema, + ); + return config.content === "plain" + ? flattenToText(parsed, schema) + : parsed; + } + : undefined; + const rules: TagParseRule[] = [ { tag: `[data-inline-content-type="${config.type}"]`, - contentElement: (element) => { - const htmlElement = element as HTMLElement; - - if (htmlElement.matches("[data-editable]")) { - return htmlElement; - } - - return htmlElement.querySelector("[data-editable]") || htmlElement; - }, + contentElement: (element) => getEditableElement(element as HTMLElement), + getContent: getContent + ? (node, schema) => + getContent(getEditableElement)(node as HTMLElement, schema) + : undefined, }, ]; @@ -121,6 +242,12 @@ export function getInlineContentParseRules( return props; }, + // Because we do the parsing ourselves, we want to preserve whitespace for + // content we've parsed. + preserveWhitespace: getContent ? true : undefined, + getContent: getContent + ? (node, schema) => getContent((el) => el)(node as HTMLElement, schema) + : undefined, }); } return rules; @@ -138,9 +265,27 @@ export function createInlineContentSpec< inline: true, group: "inline", draggable: inlineContentImplementation.meta?.draggable, - selectable: inlineContentConfig.content === "styled", + selectable: inlineContentConfig.content !== "none", atom: inlineContentConfig.content === "none", - content: inlineContentConfig.content === "styled" ? "inline*" : "", + code: inlineContentImplementation.meta?.code, + content: + inlineContentConfig.content === "styled" + ? "inline*" + : inlineContentConfig.content === "plain" + ? "text*" + : "", + // "plain" inline content holds unstyled text, so it disallows formatting + // marks (mirroring "plain" blocks). It still allows the non-formatting marks + // (comments and suggestions/diffs), which annotate content without changing + // it and are ignored by the content model. `nonFormattingMarks` resolves the + // group only when at least one such mark is registered, so a plain inline + // content in an editor without any of them doesn't reference an empty + // (unknown) mark group. + marks() { + return inlineContentConfig.content === "plain" + ? nonFormattingMarks(this.editor) + : undefined; + }, addAttributes() { return propsToAttributes(inlineContentConfig.propSchema); @@ -154,6 +299,7 @@ export function createInlineContentSpec< return getInlineContentParseRules( inlineContentConfig, inlineContentImplementation.parse, + inlineContentImplementation.parseContent, ); }, @@ -171,6 +317,8 @@ export function createInlineContentSpec< // No-op }, editor, + node, + () => undefined, ); return addInlineContentAttributes( @@ -207,6 +355,8 @@ export function createInlineContentSpec< ); }, editor, + node, + getPos, ); const nodeView = addInlineContentAttributes( @@ -233,10 +383,19 @@ export function createInlineContentSpec< ...inlineContentImplementation, toExternalHTML: inlineContentImplementation.toExternalHTML, render(inlineContent, updateInlineContent, editor) { + // Rendered outside the editor (serialization), so there's no live node + // view - derive the node from the content and stub out `getPos`. + const node = inlineContentToNodes( + [inlineContent] as any, + editor.pmSchema, + )[0]; + const output = inlineContentImplementation.render( inlineContent, updateInlineContent, editor, + node, + () => undefined, ); return addInlineContentAttributes( diff --git a/packages/core/src/schema/inlineContent/internal.ts b/packages/core/src/schema/inlineContent/internal.ts index 799c41e4f1..ab283a674d 100644 --- a/packages/core/src/schema/inlineContent/internal.ts +++ b/packages/core/src/schema/inlineContent/internal.ts @@ -1,5 +1,9 @@ import { KeyboardShortcutCommand, Node } from "@tiptap/core"; +import { + Extension, + ExtensionFactoryInstance, +} from "../../editor/BlockNoteExtension.js"; import { camelToDataKebab } from "../../util/string.js"; import { PropSchema, Props } from "../propTypes.js"; import { @@ -77,10 +81,12 @@ export function createInternalInlineContentSpec< >( config: T, implementation: InlineContentImplementation>, + extensions?: (Extension | ExtensionFactoryInstance)[], ): InlineContentSpec { return { config, implementation, + extensions, } as const; } @@ -99,12 +105,25 @@ export function createInlineContentSpecFromTipTapNode< { type: node.name as T["name"], propSchema, - content: node.config.content === "inline*" ? "styled" : "none", + content: + node.config.content === "inline*" + ? "styled" + : node.config.content === "text*" + ? "plain" + : "none", }, + // Cast needed because `implementation` is typed against the generic + // `CustomInlineContentConfig`, while `createInternalInlineContentSpec` + // expects the implementation for the specific (still-generic) config + // inferred from `node`/`propSchema` above. { ...implementation, node, - }, + } as unknown as InlineContentImplementation<{ + type: T["name"]; + propSchema: P; + content: "styled" | "none" | "plain"; + }>, ); } diff --git a/packages/core/src/schema/inlineContent/types.ts b/packages/core/src/schema/inlineContent/types.ts index e502e05ead..bcc670fe88 100644 --- a/packages/core/src/schema/inlineContent/types.ts +++ b/packages/core/src/schema/inlineContent/types.ts @@ -1,8 +1,12 @@ import { Node } from "@tiptap/core"; import { ViewMutationRecord } from "prosemirror-view"; -import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { PropSchema, Props } from "../propTypes.js"; import { StyleSchema, Styles } from "../styles/types.js"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import { + Extension, + ExtensionFactoryInstance, +} from "../../editor/BlockNoteExtension.js"; export type CustomInlineContentConfig = { type: string; @@ -16,11 +20,35 @@ export type InlineContentConfig = CustomInlineContentConfig | "text" | "link"; // InlineContentImplementation contains the "implementation" info about an InlineContent element // such as the functions / Nodes required to render and / or serialize it export type InlineContentImplementation = - T extends "link" | "text" - ? undefined - : { + T extends CustomInlineContentConfig + ? { meta?: { + /** + * Whether the inline content is a {@link https://prosemirror.net/docs/ref/#model.NodeSpec.code} block + */ + code?: boolean; + /** + * When {@link code} is `true`, this can syntax highlight the contents of the block with the result of this callback + */ + // Method syntax (rather than an arrow-function property) so its + // parameter is checked bivariantly, keeping a specific + // implementation assignable to the generic spec record type. + highlight?( + inlineContent: Pick< + CustomInlineContentFromConfig, + "type" | "props" + >, + ): string | undefined; + /** + * Whether the inline content is draggable + */ draggable?: boolean; + /** + * Marks the inline content as rendering a preview with an editable + * source popup, driven by the editor-wide + * `SourceInlineContentWithPreviewExtension`. + */ + hasPreview?: boolean; }; node: Node; toExternalHTML?: ( @@ -43,7 +71,8 @@ export type InlineContentImplementation = destroy?: () => void; }; runsBefore?: string[]; - }; + } + : undefined; export type InlineContentSchemaWithInlineContent< IType extends string, @@ -57,6 +86,7 @@ export type InlineContentSchemaWithInlineContent< export type InlineContentSpec = { config: T; implementation: InlineContentImplementation; + extensions?: (Extension | ExtensionFactoryInstance)[]; }; // A Schema contains all the types (Configs) supported in an editor diff --git a/packages/core/src/y/extensions/AttributionExtension.test.ts b/packages/core/src/y/extensions/AttributionExtension.test.ts index 3c28d50771..df0267f093 100644 --- a/packages/core/src/y/extensions/AttributionExtension.test.ts +++ b/packages/core/src/y/extensions/AttributionExtension.test.ts @@ -7,6 +7,12 @@ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import type { User } from "../../user/index.js"; import { AttributionExtension } from "./AttributionExtension.js"; +// Editors created during a test, destroyed in afterEach: an undestroyed +// EditorView leaves ProseMirror DOMObserver timers behind, which fire after +// the jsdom environment is torn down ("document is not defined" as an +// unhandled error - flaky, timing-dependent, mostly on slow CI). +const editors: BlockNoteEditor[] = []; + // A `resolveUsers` spy plus an editor with the AttributionExtension registered. // No Yjs/collaboration needed — the extension's load plugin only cares that a // transaction adds a `y-attributed-*` mark, which we do directly below. @@ -26,6 +32,7 @@ function createEditor() { extensions: [AttributionExtension({ resolveUsers })], }); editor.mount(document.createElement("div")); + editors.push(editor); return { editor, resolveUsers }; } @@ -47,6 +54,9 @@ function addInsertMark(editor: BlockNoteEditor, userIds: string[]) { describe("AttributionExtension user loading", () => { afterEach(() => { + for (const editor of editors.splice(0)) { + editor._tiptapEditor.destroy(); + } vi.restoreAllMocks(); }); diff --git a/packages/core/src/y/extensions/Versioning.test.ts b/packages/core/src/y/extensions/Versioning.test.ts index 544bfbec67..88f3ad70c5 100644 --- a/packages/core/src/y/extensions/Versioning.test.ts +++ b/packages/core/src/y/extensions/Versioning.test.ts @@ -31,15 +31,26 @@ function createInMemoryYjsEndpoints(): VersioningEndpoints { >(); const contents = new Map(); + // `Date.now()` only has millisecond resolution, so snapshots created in quick + // succession (as they are in these tests) can share a timestamp, which leaves + // the newest-first ordering of `list()` ambiguous. Hand out strictly + // increasing timestamps so creation order is always recoverable. + let lastTimestamp = 0; + function nextTimestamp() { + lastTimestamp = Math.max(Date.now(), lastTimestamp + 1); + return lastTimestamp; + } + return { list: async () => [...snapshots.values()].sort((a, b) => b.createdAt - a.createdAt), create: async (fragment, options) => { + const now = nextTimestamp(); const snapshot = { id: crypto.randomUUID(), name: options?.name, - createdAt: Date.now(), - updatedAt: Date.now(), + createdAt: now, + updatedAt: now, restoredFromSnapshotId: options?.restoredFromSnapshot?.id ? String(options.restoredFromSnapshot.id) : undefined, @@ -57,11 +68,12 @@ function createInMemoryYjsEndpoints(): VersioningEndpoints { }, restore: async (fragment, snapshot) => { // Create backup + const backupTimestamp = nextTimestamp(); const backup = { id: crypto.randomUUID(), name: "Backup", - createdAt: Date.now(), - updatedAt: Date.now(), + createdAt: backupTimestamp, + updatedAt: backupTimestamp, }; contents.set(backup.id, Y.encodeStateAsUpdateV2(fragment.doc!)); snapshots.set(backup.id, backup); @@ -70,11 +82,12 @@ function createInMemoryYjsEndpoints(): VersioningEndpoints { const tempDoc = new Y.Doc(); Y.applyUpdateV2(tempDoc, snapshotContent); + const restoredTimestamp = nextTimestamp(); const restored = { id: crypto.randomUUID(), name: "Restored Snapshot", - createdAt: Date.now(), - updatedAt: Date.now(), + createdAt: restoredTimestamp, + updatedAt: restoredTimestamp, restoredFromSnapshotId: String(snapshot.id), }; contents.set(restored.id, Y.encodeStateAsUpdateV2(tempDoc)); @@ -89,7 +102,7 @@ function createInMemoryYjsEndpoints(): VersioningEndpoints { throw new Error(`Snapshot ${String(snapshot.id)} not found`); } s.name = name; - s.updatedAt = Date.now(); + s.updatedAt = nextTimestamp(); }, }; } diff --git a/packages/core/src/yjs/extensions/Versioning.test.ts b/packages/core/src/yjs/extensions/Versioning.test.ts index e8804e9745..b3fdac78a2 100644 --- a/packages/core/src/yjs/extensions/Versioning.test.ts +++ b/packages/core/src/yjs/extensions/Versioning.test.ts @@ -304,15 +304,26 @@ function createInMemoryYjsEndpoints(): VersioningEndpoints< >(); const contents = new Map(); + // `Date.now()` only has millisecond resolution, so snapshots created in quick + // succession (as they are in these tests) can share a timestamp, which leaves + // the newest-first ordering of `list()` ambiguous. Hand out strictly + // increasing timestamps so creation order is always recoverable. + let lastTimestamp = 0; + function nextTimestamp() { + lastTimestamp = Math.max(Date.now(), lastTimestamp + 1); + return lastTimestamp; + } + return { list: async () => [...snapshots.values()].sort((a, b) => b.createdAt - a.createdAt), create: async (fragment, options) => { + const now = nextTimestamp(); const snapshot = { id: crypto.randomUUID(), name: options?.name, - createdAt: Date.now(), - updatedAt: Date.now(), + createdAt: now, + updatedAt: now, restoredFromSnapshotId: options?.restoredFromSnapshot?.id ? String(options.restoredFromSnapshot.id) : undefined, @@ -329,11 +340,12 @@ function createInMemoryYjsEndpoints(): VersioningEndpoints< return data; }, restore: async (fragment, snapshot) => { + const backupTimestamp = nextTimestamp(); const backup = { id: crypto.randomUUID(), name: "Backup", - createdAt: Date.now(), - updatedAt: Date.now(), + createdAt: backupTimestamp, + updatedAt: backupTimestamp, }; contents.set(backup.id, Y.encodeStateAsUpdate(fragment.doc!)); snapshots.set(backup.id, backup); @@ -347,7 +359,7 @@ function createInMemoryYjsEndpoints(): VersioningEndpoints< throw new Error(`Snapshot ${String(snapshot.id)} not found`); } s.name = name; - s.updatedAt = Date.now(); + s.updatedAt = nextTimestamp(); }, }; } diff --git a/packages/core/vite.config.ts b/packages/core/vite.config.ts index 603a974375..eca8efa6a5 100644 --- a/packages/core/vite.config.ts +++ b/packages/core/vite.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx b/packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx index e71e32e5a2..92beea56ae 100644 --- a/packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx +++ b/packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx @@ -1,4 +1,6 @@ -const template = () => ({ +import type { Project } from "../util"; + +const template = (_project: Project) => ({ __comment: "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", compilerOptions: { target: "ESNext", @@ -16,6 +18,9 @@ const template = () => ({ noEmit: true, jsx: "react-jsx", composite: true, + // The repo-wide alias for the shared test-utils directory (private, so it + // only resolves inside the monorepo). Harmless for examples that don't use it. + paths: { "@shared/*": ["../../../shared/*"] }, }, include: ["."], __ADD_FOR_LOCAL_DEV_references: [ diff --git a/packages/dev-scripts/examples/template-react/vite.config.ts.template.tsx b/packages/dev-scripts/examples/template-react/vite.config.ts.template.tsx index b758ff12f6..7b6f0af639 100644 --- a/packages/dev-scripts/examples/template-react/vite.config.ts.template.tsx +++ b/packages/dev-scripts/examples/template-react/vite.config.ts.template.tsx @@ -26,6 +26,10 @@ export default defineConfig(((conf: { command: string }) => ({ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) ? {} : ({ + // The repo-wide alias for the shared test-utils directory (private, + // so it only resolves inside the monorepo). Harmless for examples + // that don't use it. + "@shared": path.resolve(__dirname, "../../../shared/"), // Comment out the lines below to load a built version of blocknote // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( diff --git a/packages/dev-scripts/package.json b/packages/dev-scripts/package.json index 1d4bf58d36..5ae9805c17 100644 --- a/packages/dev-scripts/package.json +++ b/packages/dev-scripts/package.json @@ -25,7 +25,7 @@ "rimraf": "^5.0.10", "tinyglobby": "0.2.12", "tsx": "^4.20.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:" }, "dependencies": {} diff --git a/packages/dev-scripts/vite.config.ts b/packages/dev-scripts/vite.config.ts index c87eb285a6..2f5fadff19 100644 --- a/packages/dev-scripts/vite.config.ts +++ b/packages/dev-scripts/vite.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ run: { tasks: { build: { - command: "tsgo", + command: "tsc", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/diagram-block/.gitignore b/packages/diagram-block/.gitignore new file mode 100644 index 0000000000..58f115c8dc --- /dev/null +++ b/packages/diagram-block/.gitignore @@ -0,0 +1,23 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/diagram-block/LICENSE b/packages/diagram-block/LICENSE new file mode 100644 index 0000000000..fa0086a952 --- /dev/null +++ b/packages/diagram-block/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/packages/diagram-block/package.json b/packages/diagram-block/package.json new file mode 100644 index 0000000000..8ba05fb129 --- /dev/null +++ b/packages/diagram-block/package.json @@ -0,0 +1,136 @@ +{ + "name": "@blocknote/diagram-block", + "homepage": "https://github.com/TypeCellOS/BlockNote", + "private": false, + "sideEffects": [ + "*.css" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/TypeCellOS/BlockNote.git", + "directory": "packages/diagram-block" + }, + "license": "MPL-2.0", + "version": "0.51.4", + "files": [ + "dist", + "types", + "src" + ], + "keywords": [ + "react", + "javascript", + "editor", + "typescript", + "prosemirror", + "wysiwyg", + "rich-text-editor", + "notion", + "yjs", + "block-based", + "tiptap", + "mermaid", + "diagram", + "flowchart" + ], + "description": "A \"Notion-style\" block-based extensible text editor built on top of Prosemirror and Tiptap.", + "type": "module", + "source": "src/index.ts", + "types": "./types/src/index.d.ts", + "main": "./dist/blocknote-diagram-block.cjs", + "module": "./dist/blocknote-diagram-block.js", + "exports": { + ".": { + "types": "./types/src/index.d.ts", + "import": "./dist/blocknote-diagram-block.js", + "require": "./dist/blocknote-diagram-block.cjs" + }, + "./docx-exporter": { + "types": "./types/src/docx-exporter/index.d.ts", + "import": "./dist/docx-exporter.js", + "require": "./dist/docx-exporter.cjs" + }, + "./odt-exporter": { + "types": "./types/src/odt-exporter/index.d.ts", + "import": "./dist/odt-exporter.js", + "require": "./dist/odt-exporter.cjs" + }, + "./pdf-exporter": { + "types": "./types/src/pdf-exporter/index.d.ts", + "import": "./dist/pdf-exporter.js", + "require": "./dist/pdf-exporter.cjs" + }, + "./email-exporter": { + "types": "./types/src/email-exporter/index.d.ts", + "import": "./dist/email-exporter.js", + "require": "./dist/email-exporter.cjs" + } + }, + "scripts": { + "dev": "vp dev", + "lint": "vp lint src", + "test": "vp test --run", + "test-watch": "vp test watch", + "clean": "rimraf dist && rimraf types" + }, + "dependencies": { + "@blocknote/core": "workspace:^", + "@blocknote/react": "workspace:^", + "mermaid": "^11.0.0" + }, + "devDependencies": { + "@blocknote/shared": "workspace:^", + "react-icons": "^5.5.0", + "@blocknote/xl-docx-exporter": "workspace:^", + "@blocknote/xl-email-exporter": "workspace:^", + "@blocknote/xl-odt-exporter": "workspace:^", + "@blocknote/xl-pdf-exporter": "workspace:^", + "@react-email/components": "^1.0.12", + "@react-pdf/renderer": "^4.5.1", + "@types/react": "^19.2.3", + "@zip.js/zip.js": "^2.8.8", + "@types/react-dom": "^19.2.3", + "docx": "^9.6.1", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-element-to-jsx-string": "^17.0.1", + "rimraf": "^5.0.10", + "rollup-plugin-webpack-stats": "^0.2.6", + "typescript": "^7.0.2", + "vite-plus": "catalog:" + }, + "peerDependencies": { + "@blocknote/xl-docx-exporter": "workspace:^", + "@blocknote/xl-email-exporter": "workspace:^", + "@blocknote/xl-odt-exporter": "workspace:^", + "@blocknote/xl-pdf-exporter": "workspace:^", + "@react-email/components": "^1.0.12", + "@react-pdf/renderer": "^4.5.1", + "docx": "^9.6.1", + "react": "^18.0 || ^19.0 || >= 19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc" + }, + "peerDependenciesMeta": { + "@blocknote/xl-docx-exporter": { + "optional": true + }, + "@blocknote/xl-email-exporter": { + "optional": true + }, + "@blocknote/xl-odt-exporter": { + "optional": true + }, + "@blocknote/xl-pdf-exporter": { + "optional": true + }, + "@react-email/components": { + "optional": true + }, + "@react-pdf/renderer": { + "optional": true + }, + "docx": { + "optional": true + } + } +} diff --git a/packages/diagram-block/src/block/createReactDiagramBlockSpec.test.tsx b/packages/diagram-block/src/block/createReactDiagramBlockSpec.test.tsx new file mode 100644 index 0000000000..9880c30d6d --- /dev/null +++ b/packages/diagram-block/src/block/createReactDiagramBlockSpec.test.tsx @@ -0,0 +1,281 @@ +import { BlockNoteEditor, BlockNoteSchema } from "@blocknote/core"; +import { BlockNoteViewRaw } from "@blocknote/react"; +import { flushSync } from "react-dom"; +import { createRoot, Root } from "react-dom/client"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from "vite-plus/test"; +import { createReactDiagramBlockSpec } from "./createReactDiagramBlockSpec.js"; + +// TODO: deprecate jsdom, use vitest browser test? +/** + * @vitest-environment jsdom + */ + +// Mermaid needs a real browser to render, so the tests mock it - the specs +// under test cover the block's editing behavior, not Mermaid itself. Sources +// containing "INVALID" fail to parse, for testing the error states. +vi.mock("mermaid", () => ({ + default: { + initialize: vi.fn(), + parse: vi.fn(async (source: string) => { + if (source.includes("INVALID")) { + throw new Error("mock parse error"); + } + return true; + }), + render: vi.fn(async () => ({ + svg: '', + })), + }, +})); + +// The diagram block isn't a default block, so register it in a custom schema. +const schema = BlockNoteSchema.create().extend({ + blockSpecs: { diagram: createReactDiagramBlockSpec() }, +}); + +describe("Diagram block source popup", () => { + let editor: BlockNoteEditor; + let div: HTMLDivElement; + let root: Root; + + beforeEach(async () => { + // Mounted in the document tree so capture-phase handlers on ancestors see + // dispatched events. + div = document.createElement("div"); + document.body.appendChild(div); + + editor = BlockNoteEditor.create({ + schema, + trailingBlock: false, + initialContent: [ + { id: "para", type: "paragraph", content: "before" }, + { id: "diagram", type: "diagram", content: "graph TD\n A --> B" }, + ], + }); + + root = createRoot(div); + flushSync(() => { + root.render(); + }); + // Let the React node view render (and the mocked Mermaid "render") before + // assertions read its DOM. + await flush(); + + editor.setTextCursorPosition("diagram", "start"); + }); + + afterEach(() => { + root.unmount(); + editor._tiptapEditor.destroy(); + editor = undefined as any; + div.remove(); + }); + + /** Yields to the event loop so async renders & store updates can flush. */ + function flush() { + return new Promise((resolve) => setTimeout(resolve, 0)); + } + + /** The preview-with-source-popup root, which holds `data-open`. */ + function previewRoot(): HTMLElement { + return div.querySelector(".bn-preview-with-source-popup") as HTMLElement; + } + + /** Whether the source popup is open (the source is being edited). */ + function isPopupOpen(): boolean { + return previewRoot()?.getAttribute("data-open") === "true"; + } + + /** The diagram block's source as plain text. */ + function source(): string { + const block = editor.getBlock("diagram")!; + return (block.content as { text: string }[]) + .map((node) => node.text ?? "") + .join(""); + } + + /** Dispatches a keydown on the editor DOM, running the ProseMirror keymap. + * Returns whether the default was prevented. */ + function pressKey(key: string, init: KeyboardEventInit = {}): boolean { + const event = new KeyboardEvent("keydown", { + key, + bubbles: true, + cancelable: true, + ...init, + }); + editor.prosemirrorView!.dom.dispatchEvent(event); + return event.defaultPrevented; + } + + it("renders the (mocked) diagram preview", () => { + expect( + previewRoot().querySelector( + '.bn-preview-container [data-mermaid-mock="true"]', + ), + ).not.toBeNull(); + }); + + it("Enter opens the source popup", async () => { + expect(isPopupOpen()).toBe(false); + + expect(pressKey("Enter")).toBe(true); + await flush(); + + expect(isPopupOpen()).toBe(true); + }); + + it("Enter inserts a line break while the popup is open (newline enter behaviour)", async () => { + pressKey("Enter"); + await flush(); + expect(isPopupOpen()).toBe(true); + const sourceBefore = source(); + + // Unlike the (single-line) math block, Enter extends the source instead + // of closing the popup. + expect(pressKey("Enter")).toBe(true); + await flush(); + + expect(isPopupOpen()).toBe(true); + expect(source()).toBe(sourceBefore + "\n"); + }); + + it("Mod+A selects only the source while the popup is open", async () => { + pressKey("Enter"); + await flush(); + expect(isPopupOpen()).toBe(true); + + // jsdom isn't detected as macOS, so "Mod" resolves to Ctrl. + expect(pressKey("a", { ctrlKey: true })).toBe(true); + await flush(); + + // The selection spans exactly the block's source, not the document. + // Newlines are hard break nodes in the ProseMirror doc, so extract them as + // "\n" to match the block's source (where `getBlock` renders them as such). + const selection = editor.prosemirrorState.selection; + expect( + editor.prosemirrorState.doc.textBetween( + selection.from, + selection.to, + undefined, + (leafNode) => (leafNode.type.name === "hardBreak" ? "\n" : ""), + ), + ).toBe(source()); + expect(isPopupOpen()).toBe(true); + }); + + it("Escape closes the source popup", async () => { + pressKey("Enter"); + await flush(); + expect(isPopupOpen()).toBe(true); + + expect(pressKey("Escape")).toBe(true); + await flush(); + + expect(isPopupOpen()).toBe(false); + }); + + it("blocks character input while the popup is closed", () => { + expect(isPopupOpen()).toBe(false); + + // The source is hidden, so the keystroke is swallowed rather than + // silently editing the source the user can't see. + expect(pressKey("a")).toBe(true); + }); + + /** The mocked rendered diagram, if currently shown as the preview. */ + function renderedDiagram(): Element | null { + return previewRoot().querySelector( + '.bn-preview-container [data-mermaid-mock="true"]', + ); + } + + /** The compact error state, if currently shown as the preview. */ + function errorState(): Element | null { + return previewRoot().querySelector( + ".bn-preview-container .bn-preview-placeholder-error", + ); + } + + it("keeps the last preview while editing an erroneous source", async () => { + pressKey("Enter"); + await flush(); + expect(isPopupOpen()).toBe(true); + + editor.updateBlock("diagram", { content: "graph INVALID" }); + await flush(); + + // The last valid diagram stays up instead of the error state. + expect(renderedDiagram()).not.toBeNull(); + expect(errorState()).toBeNull(); + }); + + it("shows the error state once an erroneous source is committed", async () => { + pressKey("Enter"); + await flush(); + editor.updateBlock("diagram", { content: "graph INVALID" }); + await flush(); + + pressKey("Escape"); + await flush(); + expect(isPopupOpen()).toBe(false); + + // Committed with an error, so the (stale) preview is replaced by the + // error state. + expect(renderedDiagram()).toBeNull(); + expect(errorState()).not.toBeNull(); + }); + + it("keeps showing the error state when reopening a committed error", async () => { + pressKey("Enter"); + await flush(); + editor.updateBlock("diagram", { content: "graph INVALID" }); + await flush(); + pressKey("Escape"); + await flush(); + expect(errorState()).not.toBeNull(); + + pressKey("Enter"); + await flush(); + expect(isPopupOpen()).toBe(true); + + // The error was committed, so reopening doesn't bring the stale preview + // back - the error state stays until the source renders successfully. + expect(renderedDiagram()).toBeNull(); + expect(errorState()).not.toBeNull(); + }); + + it("shows the error state while editing when there's no last preview", async () => { + // A fresh block whose source never rendered successfully. + editor.removeBlocks(["diagram"]); + editor.insertBlocks( + [{ id: "broken", type: "diagram", content: "graph INVALID" }], + "para", + "after", + ); + await flush(); + + expect(renderedDiagram()).toBeNull(); + expect(errorState()).not.toBeNull(); + }); + + it("shows the placeholder for an empty source", async () => { + editor.removeBlocks(["diagram"]); + editor.insertBlocks( + [{ id: "empty", type: "diagram", content: "" }], + "para", + "after", + ); + await flush(); + + expect( + previewRoot().querySelector(".bn-preview-placeholder-text")?.textContent, + ).toBe("Add a Mermaid diagram"); + }); +}); diff --git a/packages/diagram-block/src/block/createReactDiagramBlockSpec.tsx b/packages/diagram-block/src/block/createReactDiagramBlockSpec.tsx new file mode 100644 index 0000000000..a7fd54101e --- /dev/null +++ b/packages/diagram-block/src/block/createReactDiagramBlockSpec.tsx @@ -0,0 +1,60 @@ +import { createBlockConfig } from "@blocknote/core"; +import { createReactBlockSpec } from "@blocknote/react"; + +import { + parseDiagramCodeContent, + parseDiagramCodeElement, +} from "./helpers/parse/parseDiagramCodeElement.js"; +import { DiagramBlockPreviewWithPopup } from "./helpers/render/DiagramBlockPreviewWithPopup.js"; + +export const createDiagramBlockConfig = createBlockConfig( + () => + ({ + type: "diagram" as const, + // The block is semantically a diagram; Mermaid is its (only, for now) + // rendering engine. An `engine` prop with a "mermaid" default can be + // added later without breaking stored documents. + propSchema: {}, + content: "plain" as const, + }) as const, +); + +export type DiagramBlockConfig = ReturnType; + +export const createReactDiagramBlockSpec = createReactBlockSpec( + createDiagramBlockConfig, + { + meta: { + code: true, + defining: true, + isolating: false, + // Diagram source is Mermaid, so highlight it as such when the syntax + // highlighting extension is present. + // + // NOTE: this currently has no visible effect. Shiki's Mermaid grammar is + // a Markdown injection (`injectionSelector: "L:text.html.markdown"`) that + // only tokenizes inside a ```` ```mermaid ```` fence, so it produces no + // tokens for bare Mermaid source. See + // https://github.com/shikijs/shiki/issues/973 - once that's resolved + // upstream, highlighting should start working with no change here. + highlight: () => "mermaid", + hasPreview: true, + hardBreakShortcut: "enter", + }, + parse: parseDiagramCodeElement, + parseContent: parseDiagramCodeContent, + // The code block also parses `
` elements, so the diagram's
+    // rule must be tried first to claim the `language-mermaid` ones.
+    runsBefore: ["codeBlock"],
+    render: DiagramBlockPreviewWithPopup,
+    toExternalHTML: (props) => (
+      
+        
+      
+ ), + }, +); diff --git a/packages/diagram-block/src/block/getDiagramBlockTypeSelectItems.ts b/packages/diagram-block/src/block/getDiagramBlockTypeSelectItems.ts new file mode 100644 index 0000000000..ddb8718cda --- /dev/null +++ b/packages/diagram-block/src/block/getDiagramBlockTypeSelectItems.ts @@ -0,0 +1,23 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { BlockTypeSelectItem } from "@blocknote/react"; +import { TbSitemap } from "react-icons/tb"; + +import { getDiagramDictionary } from "../i18n/dictionary.js"; + +/** + * Block type select item for the Diagram block, for use with the formatting + * toolbar's `BlockTypeSelect` (spread into its `items` alongside the defaults). + * The Diagram block lives in an optional package, so it isn't part of the + * default items - this lets consumers opt it in. The name comes from the + * editor's diagram dictionary, like the default items' names come from its + * main dictionary. + */ +export const getDiagramBlockTypeSelectItems = ( + editor: BlockNoteEditor, +): BlockTypeSelectItem[] => [ + { + name: getDiagramDictionary(editor).block_type_select.name, + type: "diagram", + icon: TbSitemap, + }, +]; diff --git a/packages/diagram-block/src/block/helpers/index.ts b/packages/diagram-block/src/block/helpers/index.ts new file mode 100644 index 0000000000..b7d92c8b36 --- /dev/null +++ b/packages/diagram-block/src/block/helpers/index.ts @@ -0,0 +1,2 @@ +export * from "./parse/index.js"; +export * from "./render/index.js"; diff --git a/packages/diagram-block/src/block/helpers/parse/index.ts b/packages/diagram-block/src/block/helpers/parse/index.ts new file mode 100644 index 0000000000..1acfba2d07 --- /dev/null +++ b/packages/diagram-block/src/block/helpers/parse/index.ts @@ -0,0 +1 @@ +export * from "./parseDiagramCodeElement.js"; diff --git a/packages/diagram-block/src/block/helpers/parse/parseDiagramCodeElement.ts b/packages/diagram-block/src/block/helpers/parse/parseDiagramCodeElement.ts new file mode 100644 index 0000000000..ae30b454bd --- /dev/null +++ b/packages/diagram-block/src/block/helpers/parse/parseDiagramCodeElement.ts @@ -0,0 +1,30 @@ +import { parsePreCodeContent } from "@blocknote/core"; + +// Parses `
` elements - the fenced code
+// block representation used by Markdown & other editors - into diagram
+// blocks.
+export const parseDiagramCodeElement = (el: HTMLElement) => {
+  if (el.tagName !== "PRE") {
+    return undefined;
+  }
+
+  const code = el.firstElementChild;
+  if (el.childElementCount !== 1 || code?.tagName !== "CODE") {
+    return undefined;
+  }
+
+  const language =
+    code.getAttribute("data-language") ||
+    code.className
+      .split(" ")
+      .find((name) => name.startsWith("language-"))
+      ?.replace("language-", "");
+
+  return language === "mermaid" ? {} : undefined;
+};
+
+// Parses the code element's text as the diagram block's source, preserving
+// line breaks.
+export const parseDiagramCodeContent = (
+  options: Parameters[0],
+) => parsePreCodeContent(options, "diagram");
diff --git a/packages/diagram-block/src/block/helpers/render/DiagramBlockPreviewWithPopup.tsx b/packages/diagram-block/src/block/helpers/render/DiagramBlockPreviewWithPopup.tsx
new file mode 100644
index 0000000000..713aaa8618
--- /dev/null
+++ b/packages/diagram-block/src/block/helpers/render/DiagramBlockPreviewWithPopup.tsx
@@ -0,0 +1,109 @@
+import {
+  PreviewPlaceholder,
+  ReactCustomBlockRenderProps,
+  SourceBlockWithPreview,
+} from "@blocknote/react";
+import mermaid from "mermaid";
+import { useEffect, useState } from "react";
+import { SiMermaid } from "react-icons/si";
+
+import { plainContentToString } from "@blocknote/core";
+import { initializeMermaid } from "../../../helpers/initializeMermaid.js";
+import { trimDiagramSVG } from "../../../helpers/trimDiagramSVG.js";
+import { getDiagramDictionary } from "../../../i18n/dictionary.js";
+import { DiagramBlockConfig } from "../../createReactDiagramBlockSpec.js";
+
+// Each render call needs its own element ID.
+let mermaidElementId = 0;
+
+/**
+ * Renders the Mermaid source to an SVG string. The current diagram (or the
+ * last valid one, when the source has an error) stays up until the new one
+ * has fully rendered, and swapping inline SVG commits in a single frame - so
+ * the preview never flashes.
+ */
+export const useMermaidSVG = (source: string) => {
+  const [svg, setSVG] = useState("");
+  const [error, setError] = useState(undefined);
+
+  useEffect(() => {
+    if (!source.trim()) {
+      setSVG("");
+      setError(undefined);
+
+      return;
+    }
+
+    initializeMermaid();
+
+    // Rendering is asynchronous, so bail out if the source changed (or the
+    // block was removed) before it finished.
+    let stale = false;
+    void (async () => {
+      // The rendered SVG carries the given ID into the document, and Mermaid
+      // removes any existing element with that ID when rendering. So each
+      // render gets a fresh ID - reusing one makes Mermaid yank the displayed
+      // diagram out of the page mid-render.
+      const id = `mermaid-preview-${mermaidElementId++}`;
+      try {
+        await mermaid.parse(source);
+        const { svg } = await mermaid.render(id, source);
+        if (!stale) {
+          setSVG(trimDiagramSVG(svg));
+          setError(undefined);
+        }
+      } catch (err) {
+        if (!stale) {
+          setError(err instanceof Error ? err.message : String(err));
+        }
+      }
+    })();
+
+    return () => {
+      stale = true;
+    };
+  }, [source]);
+
+  return { svg, error };
+};
+export const DiagramBlockPreviewWithPopup = (
+  props: ReactCustomBlockRenderProps,
+) => {
+  const source = plainContentToString(props.block.content).trim();
+  const { svg, error } = useMermaidSVG(source);
+  const dict = getDiagramDictionary(props.editor).block;
+
+  return (
+    
+        ) : undefined
+      }
+      error={error}
+      errorPreview={
+        }
+          text={dict.preview_error_text}
+        />
+      }
+      emptySourcePlaceholder={
+        } text={dict.add_source_text} />
+      }
+      sourcePlaceholder={dict.input_placeholder}
+    />
+  );
+};
diff --git a/packages/diagram-block/src/block/helpers/render/index.ts b/packages/diagram-block/src/block/helpers/render/index.ts
new file mode 100644
index 0000000000..5ac7393214
--- /dev/null
+++ b/packages/diagram-block/src/block/helpers/render/index.ts
@@ -0,0 +1 @@
+export * from "./DiagramBlockPreviewWithPopup.js";
diff --git a/packages/diagram-block/src/block/index.ts b/packages/diagram-block/src/block/index.ts
new file mode 100644
index 0000000000..cf5493bee0
--- /dev/null
+++ b/packages/diagram-block/src/block/index.ts
@@ -0,0 +1,3 @@
+export * from "./createReactDiagramBlockSpec.js";
+export * from "./getDiagramBlockTypeSelectItems.js";
+export * from "./helpers/index.js";
diff --git a/packages/diagram-block/src/docx-exporter/docxExporter.test.ts b/packages/diagram-block/src/docx-exporter/docxExporter.test.ts
new file mode 100644
index 0000000000..3c293f60ba
--- /dev/null
+++ b/packages/diagram-block/src/docx-exporter/docxExporter.test.ts
@@ -0,0 +1,216 @@
+import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
+import { en } from "@blocknote/core/locales";
+import {
+  DOCXExporter,
+  docxDefaultSchemaMappings,
+} from "@blocknote/xl-docx-exporter";
+import { BlobReader, ZipReader } from "@zip.js/zip.js";
+import { Packer } from "docx";
+import { beforeAll, describe, expect, it } from "vite-plus/test";
+
+import {
+  diagramDocument,
+  renderInvalidDiagram,
+  zipEntryContent,
+} from "../exporterTestUtil.js";
+import { createDiagramBlockMapping, diagramBlockMapping } from "./index.js";
+
+beforeAll(async () => {
+  // @ts-expect-error - Blob polyfill for Node test environment
+  globalThis.Blob = (await import("node:buffer")).Blob;
+});
+
+function createExporter(
+  diagram: ReturnType,
+  options?: ConstructorParameters[2],
+) {
+  return new DOCXExporter(
+    BlockNoteSchema.create({ blockSpecs: defaultBlockSpecs }),
+    {
+      ...docxDefaultSchemaMappings,
+      blockMapping: {
+        ...docxDefaultSchemaMappings.blockMapping,
+        diagram,
+      },
+    } as any,
+    options,
+  );
+}
+
+const documentOptions = {
+  sectionOptions: {},
+  documentOptions: {},
+  locale: "en-US",
+} as any;
+
+describe("docx exporter mappings", () => {
+  it("should render an error placeholder for invalid sources", async () => {
+    // The renderer returns invalid sources as a typed error, and the
+    // mapping renders the error placeholder, identifying the diagram by the
+    // source's first line.
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram: renderInvalidDiagram }),
+    );
+
+    const doc = await exporter.toDocxJsDocument(
+      diagramDocument,
+      documentOptions,
+    );
+    const documentXML = await zipEntryContent(
+      await Packer.toBlob(doc),
+      "word/document.xml",
+    );
+
+    expect(documentXML).toContain("Invalid diagram");
+    expect(documentXML).toContain("graph TD");
+    expect(documentXML).not.toContain("w:drawing");
+  });
+
+  it("should throw a descriptive error without a renderer outside the browser", async () => {
+    // The built-in Mermaid renderer can't work here, and silently degrading
+    // is worse than failing loudly - the error names the `renderDiagram`
+    // option to pass.
+    const exporter = createExporter(diagramBlockMapping);
+
+    await expect(
+      exporter.toDocxJsDocument(diagramDocument, documentOptions),
+    ).rejects.toThrow("pass a `renderDiagram` function");
+  });
+
+  it("should render empty diagrams as an empty paragraph", async () => {
+    // Empty source isn't an error - there's just nothing to render (and the
+    // renderer is never invoked, so no browser is needed).
+    const exporter = createExporter(diagramBlockMapping);
+
+    const doc = await exporter.toDocxJsDocument(
+      [
+        {
+          id: "1",
+          type: "diagram",
+          props: {},
+          content: [],
+          children: [],
+        },
+      ] as any,
+      documentOptions,
+    );
+    const documentXML = await zipEntryContent(
+      await Packer.toBlob(doc),
+      "word/document.xml",
+    );
+
+    expect(documentXML).not.toContain("Invalid diagram");
+    expect(documentXML).not.toContain("w:drawing");
+  });
+
+  it("should embed the image with the renderer's actual format", async () => {
+    // Renderers aren't required to produce PNGs - the embed must carry the
+    // format the image declares.
+    const exporter = createExporter(
+      createDiagramBlockMapping({
+        renderDiagram: async () => ({
+          image: {
+            mimeType: "image/jpeg",
+            data: new Uint8Array([0, 0, 0]),
+            width: 100,
+            height: 50,
+          },
+        }),
+      }),
+    );
+
+    const doc = await exporter.toDocxJsDocument(
+      diagramDocument,
+      documentOptions,
+    );
+    const entries = await new ZipReader(
+      new BlobReader(await Packer.toBlob(doc)),
+    ).getEntries();
+
+    expect(
+      entries.some((entry) => /media\/.*\.jpe?g$/.test(entry.filename)),
+    ).toBe(true);
+  });
+
+  it("should throw when the renderer produces a format DOCX can't embed", async () => {
+    // An unknown format is a renderer contract violation - mislabeling the
+    // bytes would corrupt the document, so it fails loudly instead.
+    const exporter = createExporter(
+      createDiagramBlockMapping({
+        renderDiagram: async () => ({
+          image: {
+            mimeType: "image/webp",
+            data: new Uint8Array([0, 0, 0]),
+            width: 100,
+            height: 50,
+          },
+        }),
+      }),
+    );
+
+    await expect(
+      exporter.toDocxJsDocument(diagramDocument, documentOptions),
+    ).rejects.toThrow('renderer produced "image/webp"');
+  });
+
+  it("should scale wide diagrams down to the page width", async () => {
+    // Word clips images wider than the body area at the right margin, so
+    // the display size is clamped (1200x600 -> 600x300; EMU = px * 9525).
+    const exporter = createExporter(
+      createDiagramBlockMapping({
+        renderDiagram: async () => ({
+          image: {
+            mimeType: "image/png",
+            data: new Uint8Array([0, 0, 0]),
+            width: 1200,
+            height: 600,
+          },
+        }),
+      }),
+    );
+
+    const doc = await exporter.toDocxJsDocument(
+      diagramDocument,
+      documentOptions,
+    );
+    const documentXML = await zipEntryContent(
+      await Packer.toBlob(doc),
+      "word/document.xml",
+    );
+
+    expect(documentXML).toContain('cx="5715000"');
+    expect(documentXML).toContain('cy="2857500"');
+  });
+
+  it("should render placeholders from the configured dictionary", async () => {
+    // Exporter strings are never hardcoded: the placeholder comes from the
+    // `diagram` section of the exporter's dictionary, exactly as it would
+    // from an editor dictionary (bundled English when not configured).
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram: renderInvalidDiagram }),
+      {
+        dictionary: {
+          ...en,
+          diagram: {
+            exporter: {
+              invalid_diagram: (source: string) =>
+                `Ungültiges Diagramm „${source}"`,
+            },
+          },
+        },
+      },
+    );
+
+    const doc = await exporter.toDocxJsDocument(
+      diagramDocument,
+      documentOptions,
+    );
+    const documentXML = await zipEntryContent(
+      await Packer.toBlob(doc),
+      "word/document.xml",
+    );
+
+    expect(documentXML).toContain("Ungültiges Diagramm");
+    expect(documentXML).not.toContain("Invalid diagram");
+  });
+});
diff --git a/packages/diagram-block/src/docx-exporter/index.ts b/packages/diagram-block/src/docx-exporter/index.ts
new file mode 100644
index 0000000000..6775ad09a4
--- /dev/null
+++ b/packages/diagram-block/src/docx-exporter/index.ts
@@ -0,0 +1,139 @@
+import type {
+  BlockConfig,
+  BlockFromConfigNoChildren,
+  Exporter,
+} from "@blocknote/core";
+import { plainContentToString } from "@blocknote/core";
+import { AlignmentType, ImageRun, Paragraph, TextRun } from "docx";
+
+import {
+  RenderDiagram,
+  renderDiagramToImage,
+} from "../helpers/renderDiagramToImage.js";
+import { getDiagramExporterDictionary } from "../i18n/dictionary.js";
+
+export type { RenderDiagram } from "../helpers/renderDiagramToImage.js";
+
+const MAX_WIDTH_PIXELS = 600;
+
+type DiagramBlock = BlockFromConfigNoChildren<
+  BlockConfig<"diagram", {}, "plain">,
+  any,
+  any
+>;
+
+// Mirrors the editor, which shows the error state in the preview
+// placeholder, identifying the diagram by the (first line of the) source.
+// The parser's message is deliberately NOT rendered: it's authoring detail
+// (and untranslated English) - the editor is where the author sees and
+// fixes it.
+function errorParagraph(
+  exporter: Exporter,
+  source: string,
+) {
+  return new Paragraph({
+    alignment: AlignmentType.CENTER,
+    children: [
+      new TextRun({
+        text: getDiagramExporterDictionary(exporter).invalid_diagram(
+          source.split("\n")[0],
+        ),
+        italics: true,
+        color: "999999",
+      }),
+    ],
+  });
+}
+
+/**
+ * Creates a DOCX block mapping for `@blocknote/diagram-block` that embeds
+ * diagrams as images. Rendering runs in the browser by default (Mermaid
+ * can't render outside of it); when exporting elsewhere (e.g. server-side),
+ * pass a `renderDiagram` function backed by e.g. `@mermaid-js/mermaid-cli`
+ * or a Kroki server. Invalid sources render an error placeholder (mirroring
+ * the editor):
+ *
+ * ```ts
+ * import { createDiagramBlockMapping } from "@blocknote/diagram-block/docx-exporter";
+ *
+ * new DOCXExporter(schema, {
+ *   ...docxDefaultSchemaMappings,
+ *   blockMapping: {
+ *     ...docxDefaultSchemaMappings.blockMapping,
+ *     diagram: createDiagramBlockMapping({ renderDiagram }),
+ *   },
+ * });
+ * ```
+ */
+export function createDiagramBlockMapping(options?: {
+  renderDiagram?: RenderDiagram;
+}) {
+  return async (
+    block: DiagramBlock,
+    exporter: Exporter,
+  ) => {
+    const source = plainContentToString(block.content);
+    if (!source.trim()) {
+      return new Paragraph({});
+    }
+
+    const renderDiagram =
+      options?.renderDiagram ??
+      (typeof document !== "undefined" ? renderDiagramToImage : undefined);
+    if (!renderDiagram) {
+      throw new Error(
+        "Rendering diagrams to images requires a browser. When exporting elsewhere, pass a `renderDiagram` function to `createDiagramBlockMapping` (e.g. backed by @mermaid-js/mermaid-cli or a Kroki server).",
+      );
+    }
+
+    const result = await renderDiagram(source);
+    if (result.error !== undefined) {
+      return errorParagraph(exporter, source);
+    }
+
+    // Plugged-in renderers aren't required to produce PNGs; embed with the
+    // raster format the image declares. An unknown format is a renderer
+    // contract violation - mislabeling the bytes would corrupt the document,
+    // so it propagates as an error instead.
+    const imageTypes = {
+      "image/png": "png",
+      "image/jpeg": "jpg",
+      "image/gif": "gif",
+      "image/bmp": "bmp",
+    } as const;
+    const imageType =
+      imageTypes[result.image.mimeType as keyof typeof imageTypes];
+    if (!imageType) {
+      throw new Error(
+        `DOCX embeds support png/jpeg/gif/bmp diagram images, but the renderer produced "${result.image.mimeType}".`,
+      );
+    }
+
+    // A DOCX body is ~624px wide with default margins; Word clips wider
+    // images at the right margin, so scale the display size down to fit
+    // (the image data keeps its full resolution).
+    const displayWidth = Math.min(result.image.width, MAX_WIDTH_PIXELS);
+    return new Paragraph({
+      alignment: AlignmentType.CENTER,
+      children: [
+        new ImageRun({
+          data: result.image.data,
+          type: imageType,
+          transformation: {
+            width: displayWidth,
+            height: Math.round(
+              (displayWidth / result.image.width) * result.image.height,
+            ),
+          },
+        }),
+      ],
+    });
+  };
+}
+
+/**
+ * DOCX block mapping for `@blocknote/diagram-block` with the default options
+ * - see {@link createDiagramBlockMapping}. Browser-only; when exporting
+ * elsewhere, use the factory to pass a `renderDiagram` function.
+ */
+export const diagramBlockMapping = createDiagramBlockMapping();
diff --git a/packages/diagram-block/src/email-exporter/emailExporter.test.tsx b/packages/diagram-block/src/email-exporter/emailExporter.test.tsx
new file mode 100644
index 0000000000..9676f6944c
--- /dev/null
+++ b/packages/diagram-block/src/email-exporter/emailExporter.test.tsx
@@ -0,0 +1,78 @@
+import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
+import {
+  createCIDImageDelivery,
+  ReactEmailExporter,
+  reactEmailDefaultSchemaMappings,
+} from "@blocknote/xl-email-exporter";
+import { describe, expect, it } from "vite-plus/test";
+
+import {
+  diagramDocument,
+  renderDiagram,
+  renderInvalidDiagram,
+} from "../exporterTestUtil.js";
+import { createDiagramBlockMapping, diagramBlockMapping } from "./index.js";
+
+function createExporter(diagram: ReturnType) {
+  return new ReactEmailExporter(
+    BlockNoteSchema.create({ blockSpecs: defaultBlockSpecs }),
+    {
+      ...reactEmailDefaultSchemaMappings,
+      blockMapping: {
+        ...reactEmailDefaultSchemaMappings.blockMapping,
+        diagram,
+      },
+    } as any,
+  );
+}
+
+describe("email exporter mappings", () => {
+  it("should embed the rendered diagram as a data URL image", async () => {
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram }),
+    );
+
+    const html = await exporter.toReactEmailDocument(diagramDocument);
+
+    expect(html).toContain('src="data:image/png;base64,');
+    // The Mermaid source stays available as the alt text.
+    expect(html).toContain('alt="graph TD');
+  });
+
+  it("should throw a descriptive error without a renderer outside the browser", async () => {
+    const exporter = createExporter(diagramBlockMapping);
+
+    await expect(
+      exporter.toReactEmailDocument(diagramDocument),
+    ).rejects.toThrow("pass a `renderDiagram` function");
+  });
+
+  it("should render an error placeholder for invalid sources", async () => {
+    // The renderer returns invalid sources as a typed error, and the
+    // mapping renders the error placeholder - never the raw source.
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram: renderInvalidDiagram }),
+    );
+
+    const html = await exporter.toReactEmailDocument(diagramDocument);
+
+    expect(html).toContain("Invalid diagram");
+    expect(html).toContain("graph TD");
+    expect(html).not.toContain(" {
+    const imageDelivery = createCIDImageDelivery();
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram, imageDelivery }),
+    );
+
+    const html = await exporter.toReactEmailDocument(diagramDocument);
+
+    expect(html).toContain('src="cid:diagram-1@blocknote"');
+    // The Mermaid source stays available as the alt text.
+    expect(html).toContain('alt="graph TD');
+    expect(imageDelivery.attachments).toHaveLength(1);
+    expect(imageDelivery.attachments[0].contentType).toBe("image/png");
+  });
+});
diff --git a/packages/diagram-block/src/email-exporter/index.tsx b/packages/diagram-block/src/email-exporter/index.tsx
new file mode 100644
index 0000000000..4d99388b40
--- /dev/null
+++ b/packages/diagram-block/src/email-exporter/index.tsx
@@ -0,0 +1,117 @@
+import type {
+  BlockConfig,
+  BlockFromConfigNoChildren,
+  Exporter,
+} from "@blocknote/core";
+import { plainContentToString } from "@blocknote/core";
+import {
+  dataURLImageDelivery,
+  ReactEmailImageDelivery,
+} from "@blocknote/xl-email-exporter";
+import { Img, Text } from "@react-email/components";
+
+import {
+  RenderDiagram,
+  renderDiagramToImage,
+} from "../helpers/renderDiagramToImage.js";
+import { getDiagramExporterDictionary } from "../i18n/dictionary.js";
+
+export type { RenderDiagram } from "../helpers/renderDiagramToImage.js";
+
+type DiagramBlock = BlockFromConfigNoChildren<
+  BlockConfig<"diagram", {}, "plain">,
+  any,
+  any
+>;
+
+// Emails render in containers around 600px wide.
+const MAX_WIDTH_PIXELS = 600;
+
+/**
+ * Creates an email block mapping for `@blocknote/diagram-block` that embeds
+ * diagrams as images, with the Mermaid source as the alt text. Rendering
+ * runs in the browser by default (Mermaid can't render outside of it); when
+ * exporting elsewhere (e.g. server-side email rendering), pass a
+ * `renderDiagram` function backed by e.g. `@mermaid-js/mermaid-cli` or a
+ * Kroki server. Images are embedded as data URLs by default; pass an
+ * `imageDelivery` (e.g. `createCIDImageDelivery` from
+ * `@blocknote/xl-email-exporter`) to deliver them as inline `cid:`
+ * attachments instead, which more email clients display. Invalid sources
+ * render an error placeholder (mirroring the editor):
+ *
+ * ```ts
+ * import { createDiagramBlockMapping } from "@blocknote/diagram-block/email-exporter";
+ *
+ * new ReactEmailExporter(schema, {
+ *   ...reactEmailDefaultSchemaMappings,
+ *   blockMapping: {
+ *     ...reactEmailDefaultSchemaMappings.blockMapping,
+ *     diagram: createDiagramBlockMapping({ renderDiagram, imageDelivery }),
+ *   },
+ * });
+ * ```
+ */
+export function createDiagramBlockMapping(options?: {
+  renderDiagram?: RenderDiagram;
+  imageDelivery?: ReactEmailImageDelivery;
+}) {
+  return async (
+    block: DiagramBlock,
+    exporter: Exporter,
+  ) => {
+    const source = plainContentToString(block.content);
+    if (!source.trim()) {
+      return ;
+    }
+
+    const renderDiagram =
+      options?.renderDiagram ??
+      (typeof document !== "undefined" ? renderDiagramToImage : undefined);
+    if (!renderDiagram) {
+      throw new Error(
+        "Rendering diagrams to images requires a browser. When exporting elsewhere, pass a `renderDiagram` function to `createDiagramBlockMapping` (e.g. backed by @mermaid-js/mermaid-cli or a Kroki server).",
+      );
+    }
+
+    const result = await renderDiagram(source);
+    if (result.error !== undefined) {
+      // Mirrors the editor, which shows the error state in the preview
+      // placeholder, identifying the diagram by the (first line of the)
+      // source. The parser's message is deliberately NOT rendered: it's
+      // authoring detail (and untranslated English) - the editor is where
+      // the author sees and fixes it.
+      return (
+        
+          {getDiagramExporterDictionary(exporter).invalid_diagram(
+            source.split("\n")[0],
+          )}
+        
+      );
+    }
+
+    const displayWidth = Math.min(result.image.width, MAX_WIDTH_PIXELS);
+    const src = (options?.imageDelivery ?? dataURLImageDelivery).deliver({
+      ...result.image,
+      name: "diagram",
+    });
+
+    return (
+      {source}
+    );
+  };
+}
+
+/**
+ * Email block mapping for `@blocknote/diagram-block` with the default
+ * options - see {@link createDiagramBlockMapping}. Browser-only; when
+ * exporting elsewhere, use the factory to pass a `renderDiagram` function.
+ */
+export const diagramBlockMapping = createDiagramBlockMapping();
diff --git a/packages/diagram-block/src/exporterTestUtil.ts b/packages/diagram-block/src/exporterTestUtil.ts
new file mode 100644
index 0000000000..347050d440
--- /dev/null
+++ b/packages/diagram-block/src/exporterTestUtil.ts
@@ -0,0 +1,51 @@
+import { BlobReader, FileEntry, TextWriter, ZipReader } from "@zip.js/zip.js";
+
+import type { RenderDiagram } from "./helpers/renderDiagramToImage.js";
+
+export const diagramDocument = [
+  {
+    id: "1",
+    type: "diagram",
+    props: {},
+    content: [
+      { type: "text", text: "graph TD\n  A[Start] --> B[End]", styles: {} },
+    ],
+    children: [],
+  },
+] as any;
+
+// A real (1x1 transparent) PNG: some export paths probe the image bytes for
+// metadata, so stub images must be actual PNGs.
+const pngBytes = Uint8Array.from(
+  atob(
+    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
+  ),
+  (char) => char.charCodeAt(0),
+);
+
+// A stub renderer, standing in for e.g. mermaid-cli on a server - the real
+// (browser-only) Mermaid rendering is covered by its `.browser.test`.
+export const renderDiagram: RenderDiagram = async () => ({
+  image: {
+    mimeType: "image/png",
+    data: pngBytes,
+    width: 100,
+    height: 50,
+  },
+});
+
+// A renderer reporting the expected failure: invalid Mermaid source.
+export const renderInvalidDiagram: RenderDiagram = async () => ({
+  error: "No diagram type detected",
+});
+
+export async function zipEntryContent(
+  zip: globalThis.Blob,
+  filename: string,
+): Promise {
+  const entries = await new ZipReader(new BlobReader(zip)).getEntries();
+  const entry = entries.find((e) => e.filename === filename && !e.directory) as
+    | FileEntry
+    | undefined;
+  return entry ? await entry.getData(new TextWriter()) : "";
+}
diff --git a/packages/diagram-block/src/getDiagramSlashMenuItems.tsx b/packages/diagram-block/src/getDiagramSlashMenuItems.tsx
new file mode 100644
index 0000000000..d004ecb47e
--- /dev/null
+++ b/packages/diagram-block/src/getDiagramSlashMenuItems.tsx
@@ -0,0 +1,36 @@
+import { BlockNoteEditor } from "@blocknote/core";
+import { insertOrUpdateBlockForSlashMenu } from "@blocknote/core/extensions";
+import { DefaultReactSuggestionItem } from "@blocknote/react";
+import { TbSitemap } from "react-icons/tb";
+
+import { getDiagramDictionary } from "./i18n/dictionary.js";
+
+/**
+ * Slash menu item for the Diagram block, for use with the suggestion menu
+ * (combine with the default items via `combineByGroup`). The Diagram block
+ * lives in an optional package, so the item isn't part of the defaults - this
+ * lets consumers opt it in. Only returned when the block is actually in the
+ * editor's schema.
+ */
+export function getDiagramSlashMenuItems(
+  editor: BlockNoteEditor,
+): Omit[] {
+  const items: Omit[] = [];
+
+  if ("diagram" in editor.schema.blockSchema) {
+    items.push({
+      ...getDiagramDictionary(editor).slash_menu.diagram,
+      icon: ,
+      // Inserts a starter diagram (rather than an empty source) so the
+      // preview shows something to click and edit.
+      onItemClick: () => {
+        insertOrUpdateBlockForSlashMenu(editor, {
+          type: "diagram",
+          content: "graph TD\n    A[Start] --> B[Stop]",
+        });
+      },
+    });
+  }
+
+  return items;
+}
diff --git a/packages/diagram-block/src/helpers/index.ts b/packages/diagram-block/src/helpers/index.ts
new file mode 100644
index 0000000000..b50f640471
--- /dev/null
+++ b/packages/diagram-block/src/helpers/index.ts
@@ -0,0 +1,3 @@
+export * from "./initializeMermaid.js";
+export * from "./renderDiagramToImage.js";
+export * from "./trimDiagramSVG.js";
diff --git a/packages/diagram-block/src/helpers/initializeMermaid.ts b/packages/diagram-block/src/helpers/initializeMermaid.ts
new file mode 100644
index 0000000000..11537104f7
--- /dev/null
+++ b/packages/diagram-block/src/helpers/initializeMermaid.ts
@@ -0,0 +1,18 @@
+import mermaid from "mermaid";
+
+// The diagrams are rendered manually whenever a block's source changes.
+let initialized = false;
+
+export const initializeMermaid = () => {
+  if (!initialized) {
+    initialized = true;
+    mermaid.initialize({
+      startOnLoad: false,
+      // On render errors, makes Mermaid throw right away - instead of
+      // rendering its own error graphic AND leaving its temporary render
+      // element behind in the document (it only cleans the element up with
+      // this option set). The block renders its own error UI.
+      suppressErrorRendering: true,
+    });
+  }
+};
diff --git a/packages/diagram-block/src/helpers/renderDiagramToImage.browser.test.ts b/packages/diagram-block/src/helpers/renderDiagramToImage.browser.test.ts
new file mode 100644
index 0000000000..8727efb465
--- /dev/null
+++ b/packages/diagram-block/src/helpers/renderDiagramToImage.browser.test.ts
@@ -0,0 +1,48 @@
+import { exportImageToDataURL } from "@blocknote/core";
+import { decodeAndSample } from "@shared/util/browserImageTestUtil.js";
+import { describe, expect, test } from "vite-plus/test";
+
+import { renderDiagramToImage } from "./renderDiagramToImage.js";
+
+// Browser unit tests for the browser-only Mermaid renderer - the
+// `RenderDiagram` implementation that the (node) unit suites replace with
+// stubs. Runs in the tests package's browser suite.
+describe("renderDiagramToImage", () => {
+  test(
+    "renders Mermaid source to a non-blank PNG",
+    { timeout: 15000 },
+    async () => {
+      const result = await renderDiagramToImage(
+        "graph TD\n  A[Start] --> B[End]",
+      );
+      if (result.error !== undefined) {
+        throw new Error(`Expected a successful render: ${result.error}`);
+      }
+
+      expect(result.image.mimeType).toBe("image/png");
+      expect(result.image.width).toBeGreaterThan(0);
+      const { inkedPixels, inkedFractionX, inkedFractionY } =
+        await decodeAndSample(exportImageToDataURL(result.image));
+      expect(inkedPixels).toBeGreaterThan(0);
+      // The diagram must fill the image, not sit letterboxed in a fraction
+      // of it - Mermaid crops the view box to the content bar ~8px padding,
+      // so a healthy render inks ~0.85+ of the canvas. (WebKit regression
+      // check: it lets Mermaid's inline max-width style shrink the
+      // rasterization to ~half if the renderer doesn't strip it.)
+      expect(inkedFractionX).toBeGreaterThan(0.75);
+      expect(inkedFractionY).toBeGreaterThan(0.75);
+    },
+  );
+
+  test(
+    "returns invalid Mermaid source as a typed error",
+    { timeout: 15000 },
+    async () => {
+      // The real Mermaid parse boundary: invalid source is expected (it's
+      // user input), so it comes back as a typed error rather than a throw.
+      const result = await renderDiagramToImage("not a valid diagram !!");
+
+      expect(result.error).toBeDefined();
+    },
+  );
+});
diff --git a/packages/diagram-block/src/helpers/renderDiagramToImage.ts b/packages/diagram-block/src/helpers/renderDiagramToImage.ts
new file mode 100644
index 0000000000..b3965d88b3
--- /dev/null
+++ b/packages/diagram-block/src/helpers/renderDiagramToImage.ts
@@ -0,0 +1,108 @@
+import type { ExportImage } from "@blocknote/core";
+import mermaid from "mermaid";
+
+import { initializeMermaid } from "./initializeMermaid.js";
+
+/**
+ * Renders Mermaid source to an {@link ExportImage} (with the diagram's
+ * natural dimensions in pixels). Invalid sources are expected (they're user
+ * input), so they're returned as a typed error - with a message safe to show
+ * to readers - rather than thrown; unexpected failures (environment,
+ * renderer infrastructure) throw. The default implementation
+ * ({@link renderDiagramToImage}) needs a browser - Mermaid can't render
+ * outside of it; exporters running elsewhere plug in their own (e.g. backed
+ * by `@mermaid-js/mermaid-cli` or a Kroki server).
+ */
+export type RenderDiagram = (
+  source: string,
+) => Promise<{ error?: undefined; image: ExportImage } | { error: string }>;
+
+// Each render call needs its own element ID (Mermaid removes any existing
+// document element with the given ID when rendering).
+let exportElementId = 0;
+
+// Wrap the renderer to trade sharpness for size:
+// `(source) => renderDiagramToImage(source, 4)`.
+const DEFAULT_RASTER_SCALE = 2;
+
+/**
+ * Renders the Mermaid source to a PNG {@link ExportImage} (with the
+ * diagram's natural dimensions in pixels), rasterized at `scale` times that
+ * size so it stays sharp in the exported document - e.g. to embed diagrams
+ * as images when exporting documents to PDF/DOCX/ODT. Invalid sources are
+ * returned as a typed error. Browser-only - Mermaid can't render outside of
+ * it.
+ */
+export async function renderDiagramToImage(
+  source: string,
+  scale: number = DEFAULT_RASTER_SCALE,
+): ReturnType {
+  initializeMermaid();
+
+  try {
+    await mermaid.parse(source);
+  } catch (error) {
+    // The boundary that converts Mermaid's parse throw into the typed
+    // result.
+    return { error: error instanceof Error ? error.message : String(error) };
+  }
+
+  const { svg } = await mermaid.render(
+    `diagram-export-${exportElementId++}`,
+    source,
+  );
+
+  // Mermaid sizes the SVG relatively (`width: 100%`), so it needs explicit
+  // pixel dimensions before rasterizing. These must be set on the SVG itself,
+  // not the `Image` element: the element's width/height only affect layout,
+  // while canvas rasterization uses the SVG's intrinsic size - without one,
+  // Safari falls back to a default size (and older Firefox refuses to draw
+  // the image to a canvas at all). The view box is also the only reliable
+  // source for the diagram's pixel dimensions, which the exporters need for
+  // layout.
+  const svgElement = new DOMParser().parseFromString(
+    svg,
+    "image/svg+xml",
+  ).documentElement;
+  const viewBox = svgElement.getAttribute("viewBox")?.split(/\s+/).map(Number);
+  const width = Math.ceil(viewBox?.[2] || 800);
+  const height = Math.ceil(viewBox?.[3] || 600);
+  // The scale goes into the SVG's dimensions (rather than only the canvas),
+  // so browsers rasterize the vector at the full canvas resolution instead
+  // of upscaling a 1x raster.
+  svgElement.setAttribute("width", String(width * scale));
+  svgElement.setAttribute("height", String(height * scale));
+  // Mermaid also caps the root with an inline `max-width` style (for
+  // responsive display in the editor). CSS wins over presentation attributes
+  // when computing the SVG's intrinsic size, and WebKit honors it during
+  // canvas rasterization - leaving it in renders the diagram letterboxed at
+  // a fraction of the canvas. The explicit dimensions above are the ones
+  // that must be authoritative here.
+  svgElement.removeAttribute("style");
+  const sizedSVG = new XMLSerializer().serializeToString(svgElement);
+
+  const image = new Image();
+  image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(sizedSVG)}`;
+  await image.decode();
+
+  const canvas = document.createElement("canvas");
+  canvas.width = width * scale;
+  canvas.height = height * scale;
+  canvas.getContext("2d")!.drawImage(image, 0, 0, canvas.width, canvas.height);
+
+  const blob = await new Promise((resolve) =>
+    canvas.toBlob(resolve, "image/png"),
+  );
+  if (!blob) {
+    throw new Error("Canvas produced no PNG data");
+  }
+
+  return {
+    image: {
+      mimeType: "image/png",
+      data: new Uint8Array(await blob.arrayBuffer()),
+      width,
+      height,
+    },
+  };
+}
diff --git a/packages/diagram-block/src/helpers/trimDiagramSVG.ts b/packages/diagram-block/src/helpers/trimDiagramSVG.ts
new file mode 100644
index 0000000000..7695782f1a
--- /dev/null
+++ b/packages/diagram-block/src/helpers/trimDiagramSVG.ts
@@ -0,0 +1,58 @@
+/**
+ * Trims excess vertical whitespace from a rendered Mermaid SVG. Some diagram
+ * types (e.g. `journey`) reserve far more vertical space than their content
+ * actually uses, leaving a large empty gap below the diagram. This measures
+ * the real content bounds and shrinks the SVG's `viewBox` to fit.
+ *
+ * Only the bottom is trimmed - the horizontal extent and top edge are left
+ * untouched, so nothing (arrowhead markers, titles) can get clipped. Browser-
+ * only, since measuring requires the SVG to be laid out in the document.
+ */
+export const trimDiagramSVG = (svg: string): string => {
+  // Measured offscreen - the SVG must be in the document for `getBBox`, but it
+  // shouldn't flash on screen while measuring.
+  const container = document.createElement("div");
+  container.style.position = "absolute";
+  container.style.visibility = "hidden";
+  container.style.pointerEvents = "none";
+  container.innerHTML = svg;
+
+  const svgElement = container.querySelector("svg");
+  const viewBox = svgElement?.getAttribute("viewBox")?.split(/\s+/).map(Number);
+  if (!svgElement || viewBox?.length !== 4 || viewBox.some(isNaN)) {
+    return svg;
+  }
+
+  document.body.appendChild(container);
+  try {
+    const [vbX, vbY, vbWidth, vbHeight] = viewBox;
+    const bbox = svgElement.getBBox();
+    const contentBottom = bbox.y + bbox.height;
+    // A little padding so the content doesn't sit flush against the edge.
+    const trimmedHeight = contentBottom - vbY + 8;
+
+    let changed = false;
+    if (trimmedHeight < vbHeight) {
+      svgElement.setAttribute(
+        "viewBox",
+        `${vbX} ${vbY} ${vbWidth} ${trimmedHeight}`,
+      );
+      changed = true;
+    }
+
+    // Mermaid renders most diagrams responsively (width `100%`, height derived
+    // from the view box's aspect ratio), but pins an explicit pixel height on
+    // `journey` diagrams. That fixed height makes the SVG letterbox - it stays
+    // that many pixels tall even when scaled down to fit a narrower container,
+    // leaving empty space below. Dropping it lets the height follow the view
+    // box, so the box always hugs the content.
+    if (svgElement.hasAttribute("height")) {
+      svgElement.removeAttribute("height");
+      changed = true;
+    }
+
+    return changed ? svgElement.outerHTML : svg;
+  } finally {
+    document.body.removeChild(container);
+  }
+};
diff --git a/packages/diagram-block/src/i18n/dictionary.ts b/packages/diagram-block/src/i18n/dictionary.ts
new file mode 100644
index 0000000000..b32e94f4f7
--- /dev/null
+++ b/packages/diagram-block/src/i18n/dictionary.ts
@@ -0,0 +1,38 @@
+import { BlockNoteEditor, Exporter } from "@blocknote/core";
+
+import { en } from "./locales/en.js";
+
+export type DiagramDictionary = typeof en;
+
+/**
+ * Returns the Diagram dictionary for the editor. The Diagram block is localized
+ * by merging a `diagram` dictionary into the editor's dictionary (see the
+ * exported `locales`); when the host hasn't provided one, the bundled English
+ * strings are used, so the block works without extra setup.
+ */
+export function getDiagramDictionary(
+  editor: BlockNoteEditor,
+): DiagramDictionary {
+  return (
+    ((editor.dictionary as any).diagram as DiagramDictionary | undefined) ?? en
+  );
+}
+
+/**
+ * Returns the Diagram exporter strings. Exporters are localized independently
+ * of an editor: the host passes a dictionary to the exporter's options
+ * (see `ExporterOptions.dictionary`), and the diagram strings are read from
+ * its `diagram` section - the same shape merged into editor dictionaries -
+ * falling back to the bundled English strings.
+ */
+export function getDiagramExporterDictionary(
+  exporter: Exporter,
+): DiagramDictionary["exporter"] {
+  return (
+    (
+      (exporter.options.dictionary as any)?.diagram as
+        | DiagramDictionary
+        | undefined
+    )?.exporter ?? en.exporter
+  );
+}
diff --git a/packages/diagram-block/src/i18n/locales/ar.ts b/packages/diagram-block/src/i18n/locales/ar.ts
new file mode 100644
index 0000000000..f6620d1a00
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/ar.ts
@@ -0,0 +1,25 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const ar: DiagramDictionary = {
+  block: {
+    add_source_text: "إضافة مخطط Mermaid",
+    input_placeholder: "أدخل كود المخطط",
+    preview_error_text: "مخطط غير صالح (انقر للتعديل)",
+    preview_label: "مخطط Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "مخطط",
+      subtext: "مخطط مرسوم من مصدر Mermaid",
+      aliases: ["مخطط", "رسم بياني", "مخطط انسيابي", "mermaid", "flowchart"],
+      group: "متقدم",
+    },
+  },
+  block_type_select: {
+    name: "مخطط",
+  },
+  exporter: {
+    invalid_diagram: (source: string) =>
+      `مخطط غير صالح "\u2068${source}\u2069"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/de.ts b/packages/diagram-block/src/i18n/locales/de.ts
new file mode 100644
index 0000000000..45d76b9255
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/de.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const de: DiagramDictionary = {
+  block: {
+    add_source_text: "Mermaid-Diagramm hinzufügen",
+    input_placeholder: "Diagrammcode eingeben",
+    preview_error_text: "Ungültiges Diagramm (zum Bearbeiten klicken)",
+    preview_label: "Mermaid-Diagramm",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagramm",
+      subtext: "Aus Mermaid-Quelltext gerendertes Diagramm",
+      aliases: ["mermaid", "diagramm", "flussdiagramm", "graph"],
+      group: "Erweitert",
+    },
+  },
+  block_type_select: {
+    name: "Diagramm",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Ungültiges Diagramm "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/en.ts b/packages/diagram-block/src/i18n/locales/en.ts
new file mode 100644
index 0000000000..2ac6631897
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/en.ts
@@ -0,0 +1,22 @@
+export const en = {
+  block: {
+    add_source_text: "Add a Mermaid diagram",
+    input_placeholder: "Enter diagram code",
+    preview_error_text: "Invalid diagram (click to edit)",
+    preview_label: "Mermaid diagram",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagram",
+      subtext: "Diagram rendered from Mermaid source",
+      aliases: ["mermaid", "diagram", "flowchart", "chart", "graph"],
+      group: "Advanced",
+    },
+  },
+  block_type_select: {
+    name: "Diagram",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Invalid diagram "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/es.ts b/packages/diagram-block/src/i18n/locales/es.ts
new file mode 100644
index 0000000000..0ab6270aa3
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/es.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const es: DiagramDictionary = {
+  block: {
+    add_source_text: "Agregar diagrama Mermaid",
+    input_placeholder: "Introduce el código del diagrama",
+    preview_error_text: "Diagrama no válido (haz clic para editar)",
+    preview_label: "Diagrama Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagrama",
+      subtext: "Diagrama renderizado a partir de código Mermaid",
+      aliases: ["mermaid", "diagrama", "diagrama de flujo", "gráfico"],
+      group: "Avanzado",
+    },
+  },
+  block_type_select: {
+    name: "Diagrama",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Diagrama no válido "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/fa.ts b/packages/diagram-block/src/i18n/locales/fa.ts
new file mode 100644
index 0000000000..31909ee3da
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/fa.ts
@@ -0,0 +1,25 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const fa: DiagramDictionary = {
+  block: {
+    add_source_text: "افزودن نمودار Mermaid",
+    input_placeholder: "کد نمودار را وارد کنید",
+    preview_error_text: "نمودار نامعتبر (برای ویرایش کلیک کنید)",
+    preview_label: "نمودار Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "نمودار",
+      subtext: "نمودار رسم‌شده از کد Mermaid",
+      aliases: ["نمودار", "فلوچارت", "mermaid", "چارت"],
+      group: "پیشرفته",
+    },
+  },
+  block_type_select: {
+    name: "نمودار",
+  },
+  exporter: {
+    invalid_diagram: (source: string) =>
+      `نمودار نامعتبر "\u2068${source}\u2069"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/fr.ts b/packages/diagram-block/src/i18n/locales/fr.ts
new file mode 100644
index 0000000000..ff1e34eec5
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/fr.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const fr: DiagramDictionary = {
+  block: {
+    add_source_text: "Ajouter un diagramme Mermaid",
+    input_placeholder: "Saisir le code du diagramme",
+    preview_error_text: "Diagramme non valide (cliquez pour modifier)",
+    preview_label: "Diagramme Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagramme",
+      subtext: "Diagramme généré à partir du code Mermaid",
+      aliases: ["mermaid", "diagramme", "organigramme", "graphique"],
+      group: "Avancé",
+    },
+  },
+  block_type_select: {
+    name: "Diagramme",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Diagramme non valide "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/he.ts b/packages/diagram-block/src/i18n/locales/he.ts
new file mode 100644
index 0000000000..836d11277a
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/he.ts
@@ -0,0 +1,25 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const he: DiagramDictionary = {
+  block: {
+    add_source_text: "הוסף תרשים Mermaid",
+    input_placeholder: "הזן קוד תרשים",
+    preview_error_text: "תרשים לא תקין (לחץ לעריכה)",
+    preview_label: "תרשים Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "תרשים",
+      subtext: "תרשים שנוצר מקוד Mermaid",
+      aliases: ["תרשים", "תרשים זרימה", "mermaid", "גרף"],
+      group: "מתקדם",
+    },
+  },
+  block_type_select: {
+    name: "תרשים",
+  },
+  exporter: {
+    invalid_diagram: (source: string) =>
+      `תרשים לא חוקי "\u2068${source}\u2069"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/hr.ts b/packages/diagram-block/src/i18n/locales/hr.ts
new file mode 100644
index 0000000000..18bc215220
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/hr.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const hr: DiagramDictionary = {
+  block: {
+    add_source_text: "Dodaj Mermaid dijagram",
+    input_placeholder: "Unesi kod dijagrama",
+    preview_error_text: "Neispravan dijagram (klikni za uređivanje)",
+    preview_label: "Mermaid dijagram",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Dijagram",
+      subtext: "Dijagram iscrtan iz Mermaid koda",
+      aliases: ["mermaid", "dijagram", "dijagram toka", "graf"],
+      group: "Napredno",
+    },
+  },
+  block_type_select: {
+    name: "Dijagram",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Neispravan dijagram "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/index.ts b/packages/diagram-block/src/i18n/locales/index.ts
new file mode 100644
index 0000000000..f92fca4228
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/index.ts
@@ -0,0 +1,23 @@
+export * from "./ar.js";
+export * from "./de.js";
+export * from "./en.js";
+export * from "./es.js";
+export * from "./fa.js";
+export * from "./fr.js";
+export * from "./he.js";
+export * from "./hr.js";
+export * from "./is.js";
+export * from "./it.js";
+export * from "./ja.js";
+export * from "./ko.js";
+export * from "./nl.js";
+export * from "./no.js";
+export * from "./pl.js";
+export * from "./pt.js";
+export * from "./ru.js";
+export * from "./sk.js";
+export * from "./uk.js";
+export * from "./vi.js";
+export * from "./zh.js";
+export * from "./zh-tw.js";
+export * from "./uz.js";
diff --git a/packages/diagram-block/src/i18n/locales/is.ts b/packages/diagram-block/src/i18n/locales/is.ts
new file mode 100644
index 0000000000..8f3c1f862f
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/is.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const is: DiagramDictionary = {
+  block: {
+    add_source_text: "Bæta við Mermaid-skýringarmynd",
+    input_placeholder: "Sláðu inn kóða skýringarmyndar",
+    preview_error_text: "Ógild skýringarmynd (smelltu til að breyta)",
+    preview_label: "Mermaid-skýringarmynd",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Skýringarmynd",
+      subtext: "Skýringarmynd teiknuð úr Mermaid-kóða",
+      aliases: ["mermaid", "skýringarmynd", "flæðirit", "graf"],
+      group: "Ítarlegt",
+    },
+  },
+  block_type_select: {
+    name: "Skýringarmynd",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Ógild skýringarmynd "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/it.ts b/packages/diagram-block/src/i18n/locales/it.ts
new file mode 100644
index 0000000000..19c25d464e
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/it.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const it: DiagramDictionary = {
+  block: {
+    add_source_text: "Aggiungi diagramma Mermaid",
+    input_placeholder: "Inserisci il codice del diagramma",
+    preview_error_text: "Diagramma non valido (clicca per modificare)",
+    preview_label: "Diagramma Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagramma",
+      subtext: "Diagramma generato da codice Mermaid",
+      aliases: ["mermaid", "diagramma", "diagramma di flusso", "grafico"],
+      group: "Avanzato",
+    },
+  },
+  block_type_select: {
+    name: "Diagramma",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Diagramma non valido "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/ja.ts b/packages/diagram-block/src/i18n/locales/ja.ts
new file mode 100644
index 0000000000..83225960c3
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/ja.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const ja: DiagramDictionary = {
+  block: {
+    add_source_text: "Mermaidダイアグラムを追加",
+    input_placeholder: "ダイアグラムのコードを入力",
+    preview_error_text: "無効なダイアグラム(クリックして編集)",
+    preview_label: "Mermaidダイアグラム",
+  },
+  slash_menu: {
+    diagram: {
+      title: "ダイアグラム",
+      subtext: "Mermaidソースから描画されるダイアグラム",
+      aliases: ["ダイアグラム", "図", "フローチャート", "mermaid"],
+      group: "高度なブロック",
+    },
+  },
+  block_type_select: {
+    name: "ダイアグラム",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `無効な図 "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/ko.ts b/packages/diagram-block/src/i18n/locales/ko.ts
new file mode 100644
index 0000000000..42116b64a5
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/ko.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const ko: DiagramDictionary = {
+  block: {
+    add_source_text: "Mermaid 다이어그램 추가",
+    input_placeholder: "다이어그램 코드 입력",
+    preview_error_text: "잘못된 다이어그램(클릭하여 편집)",
+    preview_label: "Mermaid 다이어그램",
+  },
+  slash_menu: {
+    diagram: {
+      title: "다이어그램",
+      subtext: "Mermaid 소스로 렌더링되는 다이어그램",
+      aliases: ["다이어그램", "순서도", "mermaid", "차트"],
+      group: "고급",
+    },
+  },
+  block_type_select: {
+    name: "다이어그램",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `잘못된 다이어그램 "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/nl.ts b/packages/diagram-block/src/i18n/locales/nl.ts
new file mode 100644
index 0000000000..e772a6a1cf
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/nl.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const nl: DiagramDictionary = {
+  block: {
+    add_source_text: "Mermaid-diagram toevoegen",
+    input_placeholder: "Voer diagramcode in",
+    preview_error_text: "Ongeldig diagram (klik om te bewerken)",
+    preview_label: "Mermaid-diagram",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagram",
+      subtext: "Diagram op basis van Mermaid-code",
+      aliases: ["mermaid", "diagram", "stroomdiagram", "grafiek"],
+      group: "Geavanceerd",
+    },
+  },
+  block_type_select: {
+    name: "Diagram",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Ongeldig diagram "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/no.ts b/packages/diagram-block/src/i18n/locales/no.ts
new file mode 100644
index 0000000000..bcdd41a01a
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/no.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const no: DiagramDictionary = {
+  block: {
+    add_source_text: "Legg til Mermaid-diagram",
+    input_placeholder: "Skriv inn diagramkode",
+    preview_error_text: "Ugyldig diagram (klikk for å redigere)",
+    preview_label: "Mermaid-diagram",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagram",
+      subtext: "Diagram generert fra Mermaid-kode",
+      aliases: ["mermaid", "diagram", "flytskjema", "graf"],
+      group: "Avansert",
+    },
+  },
+  block_type_select: {
+    name: "Diagram",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Ugyldig diagram "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/pl.ts b/packages/diagram-block/src/i18n/locales/pl.ts
new file mode 100644
index 0000000000..6af355cc6c
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/pl.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const pl: DiagramDictionary = {
+  block: {
+    add_source_text: "Dodaj diagram Mermaid",
+    input_placeholder: "Wprowadź kod diagramu",
+    preview_error_text: "Nieprawidłowy diagram (kliknij, aby edytować)",
+    preview_label: "Diagram Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagram",
+      subtext: "Diagram renderowany z kodu Mermaid",
+      aliases: ["mermaid", "diagram", "schemat blokowy", "wykres"],
+      group: "Zaawansowane",
+    },
+  },
+  block_type_select: {
+    name: "Diagram",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Nieprawidłowy diagram "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/pt.ts b/packages/diagram-block/src/i18n/locales/pt.ts
new file mode 100644
index 0000000000..3f5ea3786e
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/pt.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const pt: DiagramDictionary = {
+  block: {
+    add_source_text: "Adicionar diagrama Mermaid",
+    input_placeholder: "Insira o código do diagrama",
+    preview_error_text: "Diagrama inválido (clique para editar)",
+    preview_label: "Diagrama Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagrama",
+      subtext: "Diagrama renderizado a partir de código Mermaid",
+      aliases: ["mermaid", "diagrama", "fluxograma", "gráfico"],
+      group: "Avançado",
+    },
+  },
+  block_type_select: {
+    name: "Diagrama",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Diagrama inválido "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/ru.ts b/packages/diagram-block/src/i18n/locales/ru.ts
new file mode 100644
index 0000000000..ca396afd6f
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/ru.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const ru: DiagramDictionary = {
+  block: {
+    add_source_text: "Добавить диаграмму Mermaid",
+    input_placeholder: "Введите код диаграммы",
+    preview_error_text: "Некорректная диаграмма (нажмите, чтобы изменить)",
+    preview_label: "Диаграмма Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Диаграмма",
+      subtext: "Диаграмма, отрисованная из кода Mermaid",
+      aliases: ["mermaid", "диаграмма", "блок-схема", "график"],
+      group: "Продвинутый",
+    },
+  },
+  block_type_select: {
+    name: "Диаграмма",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Недопустимая диаграмма "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/sk.ts b/packages/diagram-block/src/i18n/locales/sk.ts
new file mode 100644
index 0000000000..80e2b4194e
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/sk.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const sk: DiagramDictionary = {
+  block: {
+    add_source_text: "Pridať diagram Mermaid",
+    input_placeholder: "Zadajte kód diagramu",
+    preview_error_text: "Neplatný diagram (kliknutím upravíte)",
+    preview_label: "Diagram Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagram",
+      subtext: "Diagram vykreslený z kódu Mermaid",
+      aliases: ["mermaid", "diagram", "vývojový diagram", "graf"],
+      group: "Pokročilé",
+    },
+  },
+  block_type_select: {
+    name: "Diagram",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Neplatný diagram "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/uk.ts b/packages/diagram-block/src/i18n/locales/uk.ts
new file mode 100644
index 0000000000..cc781734e8
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/uk.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const uk: DiagramDictionary = {
+  block: {
+    add_source_text: "Додати діаграму Mermaid",
+    input_placeholder: "Введіть код діаграми",
+    preview_error_text: "Некоректна діаграма (натисніть, щоб редагувати)",
+    preview_label: "Діаграма Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Діаграма",
+      subtext: "Діаграма, згенерована з коду Mermaid",
+      aliases: ["mermaid", "діаграма", "блок-схема", "графік"],
+      group: "Розширені",
+    },
+  },
+  block_type_select: {
+    name: "Діаграма",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Недійсна діаграма "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/uz.ts b/packages/diagram-block/src/i18n/locales/uz.ts
new file mode 100644
index 0000000000..d519da8ab4
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/uz.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const uz: DiagramDictionary = {
+  block: {
+    add_source_text: "Mermaid diagrammasini qo‘shish",
+    input_placeholder: "Diagramma kodini kiriting",
+    preview_error_text: "Yaroqsiz diagramma (tahrirlash uchun bosing)",
+    preview_label: "Mermaid diagrammasi",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Diagramma",
+      subtext: "Mermaid kodidan chizilgan diagramma",
+      aliases: ["mermaid", "diagramma", "blok-sxema", "grafik"],
+      group: "Kengaytirilgan",
+    },
+  },
+  block_type_select: {
+    name: "Diagramma",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Yaroqsiz diagramma "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/vi.ts b/packages/diagram-block/src/i18n/locales/vi.ts
new file mode 100644
index 0000000000..8f2ee8d671
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/vi.ts
@@ -0,0 +1,32 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const vi: DiagramDictionary = {
+  block: {
+    add_source_text: "Thêm sơ đồ Mermaid",
+    input_placeholder: "Nhập mã sơ đồ",
+    preview_error_text: "Sơ đồ không hợp lệ (nhấp để chỉnh sửa)",
+    preview_label: "Sơ đồ Mermaid",
+  },
+  slash_menu: {
+    diagram: {
+      title: "Sơ đồ",
+      subtext: "Sơ đồ được tạo từ mã Mermaid",
+      aliases: [
+        "mermaid",
+        "sơ đồ",
+        "lưu đồ",
+        "biểu đồ",
+        "so do",
+        "luu do",
+        "bieu do",
+      ],
+      group: "Nâng cao",
+    },
+  },
+  block_type_select: {
+    name: "Sơ đồ",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `Sơ đồ không hợp lệ "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/zh-tw.ts b/packages/diagram-block/src/i18n/locales/zh-tw.ts
new file mode 100644
index 0000000000..e903761f92
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/zh-tw.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const zhTW: DiagramDictionary = {
+  block: {
+    add_source_text: "新增 Mermaid 圖表",
+    input_placeholder: "輸入圖表原始碼",
+    preview_error_text: "無效的圖表(點擊編輯)",
+    preview_label: "Mermaid 圖表",
+  },
+  slash_menu: {
+    diagram: {
+      title: "圖表",
+      subtext: "以 Mermaid 原始碼繪製的圖表",
+      aliases: ["mermaid", "圖表", "流程圖", "示意圖"],
+      group: "進階功能",
+    },
+  },
+  block_type_select: {
+    name: "圖表",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `無效的圖表 "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/i18n/locales/zh.ts b/packages/diagram-block/src/i18n/locales/zh.ts
new file mode 100644
index 0000000000..b1b5fecb38
--- /dev/null
+++ b/packages/diagram-block/src/i18n/locales/zh.ts
@@ -0,0 +1,24 @@
+import type { DiagramDictionary } from "../dictionary.js";
+
+export const zh: DiagramDictionary = {
+  block: {
+    add_source_text: "添加 Mermaid 图表",
+    input_placeholder: "输入图表代码",
+    preview_error_text: "无效的图表(点击编辑)",
+    preview_label: "Mermaid 图表",
+  },
+  slash_menu: {
+    diagram: {
+      title: "图表",
+      subtext: "由 Mermaid 源码渲染的图表",
+      aliases: ["mermaid", "图表", "流程图", "示意图"],
+      group: "高级功能",
+    },
+  },
+  block_type_select: {
+    name: "图表",
+  },
+  exporter: {
+    invalid_diagram: (source: string) => `无效的图表 "${source}"`,
+  },
+};
diff --git a/packages/diagram-block/src/index.ts b/packages/diagram-block/src/index.ts
new file mode 100644
index 0000000000..a40dad61ff
--- /dev/null
+++ b/packages/diagram-block/src/index.ts
@@ -0,0 +1,7 @@
+import * as locales from "./i18n/locales/index.js";
+
+export { locales };
+export * from "./i18n/dictionary.js";
+export * from "./block/index.js";
+export * from "./helpers/index.js";
+export * from "./getDiagramSlashMenuItems.js";
diff --git a/packages/diagram-block/src/odt-exporter/index.ts b/packages/diagram-block/src/odt-exporter/index.ts
new file mode 100644
index 0000000000..8be789895e
--- /dev/null
+++ b/packages/diagram-block/src/odt-exporter/index.ts
@@ -0,0 +1,134 @@
+import type {
+  BlockConfig,
+  BlockFromConfigNoChildren,
+  Exporter,
+} from "@blocknote/core";
+import { exportImageToDataURL, plainContentToString } from "@blocknote/core";
+import {
+  createODTImageParagraph,
+  ODTExporter,
+} from "@blocknote/xl-odt-exporter";
+import { createElement } from "react";
+
+import {
+  RenderDiagram,
+  renderDiagramToImage,
+} from "../helpers/renderDiagramToImage.js";
+import { getDiagramExporterDictionary } from "../i18n/dictionary.js";
+
+export type { RenderDiagram } from "../helpers/renderDiagramToImage.js";
+
+type DiagramBlock = BlockFromConfigNoChildren<
+  BlockConfig<"diagram", {}, "plain">,
+  any,
+  any
+>;
+
+// Mirrors the editor, which shows the error state in the preview
+// placeholder, identifying the diagram by the (first line of the) source.
+// The parser's message is deliberately NOT rendered: it's authoring detail
+// (and untranslated English) - the editor is where the author sees and
+// fixes it. Styled muted like the
+// other exporters' placeholders.
+function errorMessage(
+  exporter: ODTExporter,
+  source: string,
+): string {
+  return getDiagramExporterDictionary(exporter).invalid_diagram(
+    source.split("\n")[0],
+  );
+}
+
+function errorParagraph(source: string, exporter: ODTExporter) {
+  const styleName = exporter.registerStyle((name) =>
+    createElement(
+      "style:style",
+      { "style:family": "text", "style:name": name },
+      createElement("style:text-properties", {
+        "fo:font-style": "italic",
+        "fo:color": "#999999",
+      }),
+    ),
+  );
+
+  return createElement(
+    "text:p",
+    null,
+    createElement(
+      "text:span",
+      { "text:style-name": styleName },
+      errorMessage(exporter, source),
+    ),
+  );
+}
+
+/**
+ * Creates an ODT block mapping for `@blocknote/diagram-block` that embeds
+ * diagrams as images. Rendering runs in the browser by default (Mermaid
+ * can't render outside of it); when exporting elsewhere (e.g. server-side),
+ * pass a `renderDiagram` function backed by e.g. `@mermaid-js/mermaid-cli`
+ * or a Kroki server. Invalid sources render an error placeholder (mirroring
+ * the editor):
+ *
+ * ```ts
+ * import { createDiagramBlockMapping } from "@blocknote/diagram-block/odt-exporter";
+ *
+ * new ODTExporter(schema, {
+ *   ...odtDefaultSchemaMappings,
+ *   blockMapping: {
+ *     ...odtDefaultSchemaMappings.blockMapping,
+ *     diagram: createDiagramBlockMapping({ renderDiagram }),
+ *   },
+ * });
+ * ```
+ */
+export function createDiagramBlockMapping(options?: {
+  renderDiagram?: RenderDiagram;
+}) {
+  return async (
+    block: DiagramBlock,
+    exporter: Exporter,
+  ) => {
+    // Only the ODTExporter invokes ODT mappings, but mapping signatures are
+    // contravariant in the exporter parameter, so requiring the subclass here
+    // wouldn't satisfy the mapping type - hence the base type + cast.
+    const odtExporter = exporter as ODTExporter;
+    const source = plainContentToString(block.content);
+    if (!source.trim()) {
+      return createElement("text:p");
+    }
+
+    const renderDiagram =
+      options?.renderDiagram ??
+      (typeof document !== "undefined" ? renderDiagramToImage : undefined);
+    if (!renderDiagram) {
+      throw new Error(
+        "Rendering diagrams to images requires a browser. When exporting elsewhere, pass a `renderDiagram` function to `createDiagramBlockMapping` (e.g. backed by @mermaid-js/mermaid-cli or a Kroki server).",
+      );
+    }
+
+    const result = await renderDiagram(source);
+    if (result.error !== undefined) {
+      return errorParagraph(source, odtExporter);
+    }
+
+    // The image may be rendered above its display size for sharpness, so
+    // pass the diagram's display dimensions rather than the picture's own.
+    return await createODTImageParagraph(
+      odtExporter,
+      exportImageToDataURL(result.image),
+      {
+        width: result.image.width,
+        height: result.image.height,
+        align: "center",
+      },
+    );
+  };
+}
+
+/**
+ * ODT block mapping for `@blocknote/diagram-block` with the default options
+ * - see {@link createDiagramBlockMapping}. Browser-only; when exporting
+ * elsewhere, use the factory to pass a `renderDiagram` function.
+ */
+export const diagramBlockMapping = createDiagramBlockMapping();
diff --git a/packages/diagram-block/src/odt-exporter/odtExporter.test.ts b/packages/diagram-block/src/odt-exporter/odtExporter.test.ts
new file mode 100644
index 0000000000..9c5ee00c50
--- /dev/null
+++ b/packages/diagram-block/src/odt-exporter/odtExporter.test.ts
@@ -0,0 +1,77 @@
+import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
+import {
+  ODTExporter,
+  odtDefaultSchemaMappings,
+} from "@blocknote/xl-odt-exporter";
+import { BlobReader, ZipReader } from "@zip.js/zip.js";
+import { beforeAll, describe, expect, it } from "vite-plus/test";
+
+import {
+  diagramDocument,
+  renderDiagram,
+  renderInvalidDiagram,
+  zipEntryContent,
+} from "../exporterTestUtil.js";
+import { createDiagramBlockMapping, diagramBlockMapping } from "./index.js";
+
+beforeAll(async () => {
+  // @ts-expect-error - Blob polyfill for Node test environment
+  globalThis.Blob = (await import("node:buffer")).Blob;
+});
+
+function createExporter(diagram: ReturnType) {
+  const mappings = {
+    ...odtDefaultSchemaMappings,
+    blockMapping: {
+      ...odtDefaultSchemaMappings.blockMapping,
+      diagram,
+    },
+  };
+  return new ODTExporter(
+    BlockNoteSchema.create({ blockSpecs: defaultBlockSpecs }),
+    mappings as any,
+  );
+}
+
+describe("odt exporter mappings", () => {
+  it("should embed the rendered diagram as an image", async () => {
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram }),
+    );
+
+    const odt = await exporter.toODTDocument(diagramDocument);
+    const contentXML = await zipEntryContent(odt, "content.xml");
+
+    expect(contentXML).toContain("draw:image");
+    // The picture bytes are stored as their own zip entry.
+    const entries = await new ZipReader(new BlobReader(odt)).getEntries();
+    expect(
+      entries.some((entry) => entry.filename.startsWith("Pictures/")),
+    ).toBe(true);
+  });
+
+  it("should render an error placeholder for invalid sources", async () => {
+    // The renderer returns invalid sources as a typed error, and the
+    // mapping renders the error placeholder - never the raw source.
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram: renderInvalidDiagram }),
+    );
+
+    const contentXML = await zipEntryContent(
+      await exporter.toODTDocument(diagramDocument),
+      "content.xml",
+    );
+
+    expect(contentXML).toContain("Invalid diagram");
+    expect(contentXML).toContain("graph TD");
+    expect(contentXML).not.toContain("draw:image");
+  });
+
+  it("should throw a descriptive error without a renderer outside the browser", async () => {
+    const exporter = createExporter(diagramBlockMapping);
+
+    await expect(exporter.toODTDocument(diagramDocument)).rejects.toThrow(
+      "pass a `renderDiagram` function",
+    );
+  });
+});
diff --git a/packages/diagram-block/src/pdf-exporter/index.tsx b/packages/diagram-block/src/pdf-exporter/index.tsx
new file mode 100644
index 0000000000..6853ac7a96
--- /dev/null
+++ b/packages/diagram-block/src/pdf-exporter/index.tsx
@@ -0,0 +1,112 @@
+import type {
+  BlockConfig,
+  BlockFromConfigNoChildren,
+  Exporter,
+} from "@blocknote/core";
+import { exportImageToDataURL, plainContentToString } from "@blocknote/core";
+import { Image, Text, View } from "@react-pdf/renderer";
+
+import {
+  RenderDiagram,
+  renderDiagramToImage,
+} from "../helpers/renderDiagramToImage.js";
+import { getDiagramExporterDictionary } from "../i18n/dictionary.js";
+
+export type { RenderDiagram } from "../helpers/renderDiagramToImage.js";
+
+type DiagramBlock = BlockFromConfigNoChildren<
+  BlockConfig<"diagram", {}, "plain">,
+  any,
+  any
+>;
+
+const PIXELS_PER_POINT = 0.75;
+const MAX_WIDTH_POINTS = 400;
+
+// Mirrors the editor, which shows the error state in the preview
+// placeholder, identifying the diagram by the (first line of the) source.
+// The parser's message is deliberately NOT rendered: it's authoring detail
+// (and untranslated English) - the editor is where the author sees and
+// fixes it.
+function errorText(
+  exporter: Exporter,
+  source: string,
+) {
+  return (
+    
+      
+        {getDiagramExporterDictionary(exporter).invalid_diagram(
+          source.split("\n")[0],
+        )}
+      
+    
+  );
+}
+
+/**
+ * Creates a PDF block mapping for `@blocknote/diagram-block` that embeds
+ * diagrams as images. Rendering runs in the browser by default (Mermaid
+ * can't render outside of it); when exporting elsewhere (e.g. server-side),
+ * pass a `renderDiagram` function backed by e.g. `@mermaid-js/mermaid-cli`
+ * or a Kroki server. Invalid sources render an error placeholder (mirroring
+ * the editor):
+ *
+ * ```ts
+ * import { createDiagramBlockMapping } from "@blocknote/diagram-block/pdf-exporter";
+ *
+ * new PDFExporter(schema, {
+ *   ...pdfDefaultSchemaMappings,
+ *   blockMapping: {
+ *     ...pdfDefaultSchemaMappings.blockMapping,
+ *     diagram: createDiagramBlockMapping({ renderDiagram }),
+ *   },
+ * });
+ * ```
+ */
+export function createDiagramBlockMapping(options?: {
+  renderDiagram?: RenderDiagram;
+}) {
+  return async (
+    block: DiagramBlock,
+    exporter: Exporter,
+  ) => {
+    const source = plainContentToString(block.content);
+    if (!source.trim()) {
+      return ;
+    }
+
+    const renderDiagram =
+      options?.renderDiagram ??
+      (typeof document !== "undefined" ? renderDiagramToImage : undefined);
+    if (!renderDiagram) {
+      throw new Error(
+        "Rendering diagrams to images requires a browser. When exporting elsewhere, pass a `renderDiagram` function to `createDiagramBlockMapping` (e.g. backed by @mermaid-js/mermaid-cli or a Kroki server).",
+      );
+    }
+
+    const result = await renderDiagram(source);
+    if (result.error !== undefined) {
+      return errorText(exporter, source);
+    }
+
+    return (
+      
+    );
+  };
+}
+
+/**
+ * PDF block mapping for `@blocknote/diagram-block` with the default options
+ * - see {@link createDiagramBlockMapping}. Browser-only; when exporting
+ * elsewhere, use the factory to pass a `renderDiagram` function.
+ */
+export const diagramBlockMapping = createDiagramBlockMapping();
diff --git a/packages/diagram-block/src/pdf-exporter/pdfExporter.test.tsx b/packages/diagram-block/src/pdf-exporter/pdfExporter.test.tsx
new file mode 100644
index 0000000000..ee7f6d1f74
--- /dev/null
+++ b/packages/diagram-block/src/pdf-exporter/pdfExporter.test.tsx
@@ -0,0 +1,68 @@
+import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
+import {
+  PDFExporter,
+  pdfDefaultSchemaMappings,
+} from "@blocknote/xl-pdf-exporter";
+import reactElementToJSXString from "react-element-to-jsx-string";
+import { describe, expect, it } from "vite-plus/test";
+
+import {
+  diagramDocument,
+  renderDiagram,
+  renderInvalidDiagram,
+} from "../exporterTestUtil.js";
+import { createDiagramBlockMapping, diagramBlockMapping } from "./index.js";
+
+function createExporter(diagram: ReturnType) {
+  const mappings = {
+    ...pdfDefaultSchemaMappings,
+    blockMapping: {
+      ...pdfDefaultSchemaMappings.blockMapping,
+      diagram,
+    },
+  };
+  return new PDFExporter(
+    BlockNoteSchema.create({ blockSpecs: defaultBlockSpecs }),
+    mappings as any,
+  );
+}
+
+describe("pdf exporter mappings", () => {
+  it("should embed the rendered diagram as an image", async () => {
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram }),
+    );
+
+    const str = reactElementToJSXString(
+      await exporter.toReactPDFDocument(diagramDocument),
+    );
+
+    expect(str).toContain("data:image/png;base64,");
+    // 100px wide at 0.75 points per pixel.
+    expect(str).toContain("width: 75");
+  });
+
+  it("should throw a descriptive error without a renderer outside the browser", async () => {
+    const exporter = createExporter(diagramBlockMapping);
+
+    await expect(exporter.toReactPDFDocument(diagramDocument)).rejects.toThrow(
+      "pass a `renderDiagram` function",
+    );
+  });
+
+  it("should render an error placeholder for invalid sources", async () => {
+    // The renderer returns invalid sources as a typed error, and the
+    // mapping renders the error placeholder - never the raw source.
+    const exporter = createExporter(
+      createDiagramBlockMapping({ renderDiagram: renderInvalidDiagram }),
+    );
+
+    const str = reactElementToJSXString(
+      await exporter.toReactPDFDocument(diagramDocument),
+    );
+
+    expect(str).toContain("Invalid diagram");
+    expect(str).toContain("graph TD");
+    expect(str).not.toContain("data:image");
+  });
+});
diff --git a/packages/diagram-block/src/vite-env.d.ts b/packages/diagram-block/src/vite-env.d.ts
new file mode 100644
index 0000000000..bc2d8a36f3
--- /dev/null
+++ b/packages/diagram-block/src/vite-env.d.ts
@@ -0,0 +1 @@
+/// 
diff --git a/packages/diagram-block/tsconfig.json b/packages/diagram-block/tsconfig.json
new file mode 100644
index 0000000000..2d8bcd4a25
--- /dev/null
+++ b/packages/diagram-block/tsconfig.json
@@ -0,0 +1,33 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ESNext", "DOM"],
+    "moduleResolution": "bundler",
+    "jsx": "react-jsx",
+    "strict": true,
+    "sourceMap": true,
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "noEmit": false,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noImplicitReturns": true,
+    "outDir": "dist",
+    "declaration": true,
+    "declarationDir": "types",
+    "composite": true,
+    "skipLibCheck": true,
+    "emitDeclarationOnly": true,
+    "paths": {
+      "@shared/*": ["../../shared/*"]
+    }
+  },
+  "include": ["src"],
+  "references": [
+    {
+      "path": "../../shared"
+    }
+  ]
+}
diff --git a/packages/diagram-block/vite.config.ts b/packages/diagram-block/vite.config.ts
new file mode 100644
index 0000000000..698c340d58
--- /dev/null
+++ b/packages/diagram-block/vite.config.ts
@@ -0,0 +1,135 @@
+import * as path from "path";
+import { webpackStats } from "rollup-plugin-webpack-stats";
+import { configDefaults, defineConfig, type UserConfig } from "vite-plus";
+import pkg from "./package.json";
+
+// https://vitejs.dev/config/
+export default defineConfig(
+  (conf) =>
+    ({
+      run: {
+        tasks: {
+          build: {
+            command: "tsc && vp build",
+            input: [
+              { auto: true },
+              { pattern: "!**/*.tsbuildinfo", base: "workspace" },
+            ],
+            output: ["dist/**", "!dist/*.tsbuildinfo"],
+          },
+        },
+      },
+      test: {
+        setupFiles: ["./vitestSetup.ts"],
+        // `.browser.test` files need a real browser; the tests package's
+        // browser suite runs them.
+        exclude: [...configDefaults.exclude, "**/*.browser.test.*"],
+      },
+      // The ODT exporter sources (loaded via the test aliases) use JSX
+      // namespace tags (e.g. ), which Vite's oxc rejects by default.
+      oxc: {
+        jsx: {
+          throwIfNamespace: false,
+        },
+      },
+      plugins: [webpackStats() as any],
+      // used so that vitest resolves the core package from the sources instead of the built version
+      resolve: {
+        alias:
+          conf.command === "build"
+            ? ({} as Record)
+            : ({
+                "@shared": path.resolve(__dirname, "../../shared/"),
+                // load live from sources with live reload working
+                "@blocknote/core": path.resolve(__dirname, "../core/src/"),
+                "@blocknote/react": path.resolve(__dirname, "../react/src/"),
+                "@blocknote/xl-docx-exporter": path.resolve(
+                  __dirname,
+                  "../xl-docx-exporter/src/",
+                ),
+                "@blocknote/xl-email-exporter": path.resolve(
+                  __dirname,
+                  "../xl-email-exporter/src/",
+                ),
+                "@blocknote/xl-multi-column": path.resolve(
+                  __dirname,
+                  "../xl-multi-column/src/",
+                ),
+                "@blocknote/xl-odt-exporter": path.resolve(
+                  __dirname,
+                  "../xl-odt-exporter/src/",
+                ),
+                "@blocknote/xl-pdf-exporter": path.resolve(
+                  __dirname,
+                  "../xl-pdf-exporter/src/",
+                ),
+              } as Record),
+      },
+      build: {
+        sourcemap: true,
+        lib: {
+          entry: {
+            "blocknote-diagram-block": path.resolve(__dirname, "src/index.ts"),
+            "docx-exporter": path.resolve(
+              __dirname,
+              "src/docx-exporter/index.ts",
+            ),
+            "odt-exporter": path.resolve(
+              __dirname,
+              "src/odt-exporter/index.ts",
+            ),
+            "pdf-exporter": path.resolve(
+              __dirname,
+              "src/pdf-exporter/index.tsx",
+            ),
+            "email-exporter": path.resolve(
+              __dirname,
+              "src/email-exporter/index.tsx",
+            ),
+          },
+          name: "blocknote-diagram-block",
+          formats: ["es", "cjs"],
+          fileName: (format, entryName) =>
+            format === "es" ? `${entryName}.js` : `${entryName}.cjs`,
+        },
+        rollupOptions: {
+          // make sure to externalize deps that shouldn't be bundled
+          // into your library
+          external: (source) => {
+            // Bundle react-icons into the output (tree-shaken) so consumers
+            // don't need to install it as a peer/runtime dependency.
+            const bundledDeps = ["react-icons"];
+            if (
+              bundledDeps.some(
+                (dep) => source === dep || source.startsWith(dep + "/"),
+              )
+            ) {
+              return false;
+            }
+            if (
+              Object.keys({
+                ...pkg.dependencies,
+                ...((pkg as any).peerDependencies || {}),
+                ...pkg.devDependencies,
+              }).some((dep) => source === dep || source.startsWith(dep + "/"))
+            ) {
+              return true;
+            }
+            return (
+              source.startsWith("react/") ||
+              source.startsWith("react-dom/") ||
+              source.startsWith("prosemirror-") ||
+              source.startsWith("@tiptap/") ||
+              source.startsWith("@blocknote/") ||
+              source.startsWith("node:")
+            );
+          },
+          output: {
+            // Provide global variables to use in the UMD build
+            // for externalized deps
+            globals: {},
+          },
+        },
+      },
+    }) as UserConfig,
+);
diff --git a/packages/diagram-block/vitestSetup.ts b/packages/diagram-block/vitestSetup.ts
new file mode 100644
index 0000000000..dbcf3eb39c
--- /dev/null
+++ b/packages/diagram-block/vitestSetup.ts
@@ -0,0 +1,10 @@
+import { afterEach, beforeEach } from "vite-plus/test";
+
+beforeEach(() => {
+  globalThis.window = globalThis.window || ({} as any);
+  (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
+});
+
+afterEach(() => {
+  delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
+});
diff --git a/packages/mantine/package.json b/packages/mantine/package.json
index 6fb73d041b..4d7744994f 100644
--- a/packages/mantine/package.json
+++ b/packages/mantine/package.json
@@ -72,7 +72,7 @@
     "react-dom": "^19.2.5",
     "rimraf": "^5.0.10",
     "rollup-plugin-webpack-stats": "^0.2.6",
-    "typescript": "^5.9.3",
+    "typescript": "^7.0.2",
     "vite-plugin-externalize-deps": "^0.10.0",
     "vite-plus": "catalog:"
   },
diff --git a/packages/mantine/vite.config.ts b/packages/mantine/vite.config.ts
index 8d36116b79..88cf0b5dd4 100644
--- a/packages/mantine/vite.config.ts
+++ b/packages/mantine/vite.config.ts
@@ -12,7 +12,7 @@ export default defineConfig(
       run: {
         tasks: {
           build: {
-            command: "tsgo && vp build",
+            command: "tsc && vp build",
             input: [
               { auto: true },
               { pattern: "!**/*.tsbuildinfo", base: "workspace" },
diff --git a/packages/math-block/.gitignore b/packages/math-block/.gitignore
new file mode 100644
index 0000000000..58f115c8dc
--- /dev/null
+++ b/packages/math-block/.gitignore
@@ -0,0 +1,23 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/packages/math-block/LICENSE b/packages/math-block/LICENSE
new file mode 100644
index 0000000000..fa0086a952
--- /dev/null
+++ b/packages/math-block/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
\ No newline at end of file
diff --git a/packages/math-block/package.json b/packages/math-block/package.json
new file mode 100644
index 0000000000..3745c49b42
--- /dev/null
+++ b/packages/math-block/package.json
@@ -0,0 +1,156 @@
+{
+  "name": "@blocknote/math-block",
+  "homepage": "https://github.com/TypeCellOS/BlockNote",
+  "private": false,
+  "sideEffects": [
+    "*.css"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/TypeCellOS/BlockNote.git",
+    "directory": "packages/math-block"
+  },
+  "license": "MPL-2.0",
+  "version": "0.51.4",
+  "files": [
+    "dist",
+    "types",
+    "src"
+  ],
+  "keywords": [
+    "react",
+    "javascript",
+    "editor",
+    "typescript",
+    "prosemirror",
+    "wysiwyg",
+    "rich-text-editor",
+    "notion",
+    "yjs",
+    "block-based",
+    "tiptap",
+    "math",
+    "latex",
+    "mathml"
+  ],
+  "description": "A \"Notion-style\" block-based extensible text editor built on top of Prosemirror and Tiptap.",
+  "type": "module",
+  "source": "src/index.ts",
+  "types": "./types/src/index.d.ts",
+  "main": "./dist/blocknote-math-block.cjs",
+  "module": "./dist/blocknote-math-block.js",
+  "exports": {
+    ".": {
+      "types": "./types/src/index.d.ts",
+      "import": "./dist/blocknote-math-block.js",
+      "require": "./dist/blocknote-math-block.cjs"
+    },
+    "./docx-exporter": {
+      "types": "./types/src/docx-exporter/index.d.ts",
+      "import": "./dist/docx-exporter.js",
+      "require": "./dist/docx-exporter.cjs"
+    },
+    "./odt-exporter": {
+      "types": "./types/src/odt-exporter/index.d.ts",
+      "import": "./dist/odt-exporter.js",
+      "require": "./dist/odt-exporter.cjs"
+    },
+    "./pdf-exporter": {
+      "types": "./types/src/pdf-exporter/index.d.ts",
+      "import": "./dist/pdf-exporter.js",
+      "require": "./dist/pdf-exporter.cjs"
+    },
+    "./email-exporter": {
+      "types": "./types/src/email-exporter/index.d.ts",
+      "import": "./dist/email-exporter.js",
+      "require": "./dist/email-exporter.cjs"
+    }
+  },
+  "scripts": {
+    "dev": "vp dev",
+    "lint": "vp lint src",
+    "test": "vp test --run",
+    "test-watch": "vp test watch",
+    "clean": "rimraf dist && rimraf types"
+  },
+  "dependencies": {
+    "@blocknote/core": "workspace:^",
+    "@blocknote/react": "workspace:^",
+    "@handlewithcare/prosemirror-inputrules": "^0.1.4",
+    "katex": "^0.16.11",
+    "prosemirror-model": "^1.25.4",
+    "prosemirror-state": "^1.4.4"
+  },
+  "devDependencies": {
+    "@blocknote/shared": "workspace:^",
+    "react-icons": "^5.5.0",
+    "@blocknote/xl-docx-exporter": "workspace:^",
+    "@blocknote/xl-email-exporter": "workspace:^",
+    "@blocknote/xl-odt-exporter": "workspace:^",
+    "@blocknote/xl-pdf-exporter": "workspace:^",
+    "@react-email/components": "^1.0.12",
+    "@react-pdf/math": "^2.0.1",
+    "@react-pdf/renderer": "^4.5.1",
+    "@types/katex": "^0.16.7",
+    "mathml2omml": "^0.5.0",
+    "@types/react": "^19.2.3",
+    "@types/react-dom": "^19.2.3",
+    "@zip.js/zip.js": "^2.8.8",
+    "docx": "^9.6.1",
+    "mathjax-full": "^3.2.2",
+    "react": "^19.2.5",
+    "react-dom": "^19.2.5",
+    "react-element-to-jsx-string": "^17.0.1",
+    "rimraf": "^5.0.10",
+    "rollup-plugin-webpack-stats": "^0.2.6",
+    "typescript": "^7.0.2",
+    "vite-plus": "catalog:",
+    "xml-formatter": "^3.6.7"
+  },
+  "peerDependencies": {
+    "@blocknote/xl-docx-exporter": "workspace:^",
+    "@blocknote/xl-email-exporter": "workspace:^",
+    "@blocknote/xl-odt-exporter": "workspace:^",
+    "@blocknote/xl-pdf-exporter": "workspace:^",
+    "@react-email/components": "^1.0.12",
+    "@react-pdf/math": "^2.0.0",
+    "@react-pdf/renderer": "^4.5.1",
+    "docx": "^9.6.1",
+    "mathjax-full": "^3.2.2",
+    "mathml2omml": "^0.5.0",
+    "react": "^18.0 || ^19.0 || >= 19.0.0-rc",
+    "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc"
+  },
+  "peerDependenciesMeta": {
+    "@blocknote/xl-docx-exporter": {
+      "optional": true
+    },
+    "@blocknote/xl-email-exporter": {
+      "optional": true
+    },
+    "@blocknote/xl-odt-exporter": {
+      "optional": true
+    },
+    "@blocknote/xl-pdf-exporter": {
+      "optional": true
+    },
+    "@react-email/components": {
+      "optional": true
+    },
+    "@react-pdf/math": {
+      "optional": true
+    },
+    "@react-pdf/renderer": {
+      "optional": true
+    },
+    "docx": {
+      "optional": true
+    },
+    "mathjax-full": {
+      "optional": true
+    },
+    "mathml2omml": {
+      "optional": true
+    }
+  }
+}
diff --git a/packages/math-block/src/block/createReactMathBlockSpec.test.tsx b/packages/math-block/src/block/createReactMathBlockSpec.test.tsx
new file mode 100644
index 0000000000..d2d2e31796
--- /dev/null
+++ b/packages/math-block/src/block/createReactMathBlockSpec.test.tsx
@@ -0,0 +1,373 @@
+import { BlockNoteEditor, BlockNoteSchema } from "@blocknote/core";
+import { BlockNoteViewRaw } from "@blocknote/react";
+import { flushSync } from "react-dom";
+import { createRoot, Root } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
+import { createReactMathBlockSpec } from "./createReactMathBlockSpec.js";
+
+/**
+ * @vitest-environment jsdom
+ */
+
+// The math block isn't a default block, so register it in a custom schema.
+const schema = BlockNoteSchema.create().extend({
+  blockSpecs: { mathBlock: createReactMathBlockSpec() },
+});
+
+describe("Math block source popup keyboard handling", () => {
+  let editor: BlockNoteEditor;
+  let div: HTMLDivElement;
+  let root: Root;
+
+  beforeEach(() => {
+    // jsdom doesn't implement `elementFromPoint`, which ProseMirror's mousedown
+    // handler calls to map coordinates to a document position. The click tests
+    // dispatch mouse events, so stub it out (returning `null` makes ProseMirror
+    // bail gracefully) to avoid an uncaught error.
+    if (!document.elementFromPoint) {
+      document.elementFromPoint = () => null;
+    }
+
+    // The keyboard handler listens on the editor DOM (capture phase), so the
+    // mount point must be in the document tree for dispatched keydowns to reach
+    // it - a detached element's events never propagate to `document`.
+    div = document.createElement("div");
+    document.body.appendChild(div);
+
+    editor = BlockNoteEditor.create({ schema });
+
+    // Rendered the same way as the inline math test: a `BlockNoteViewRaw` into a
+    // div, so the React node view mounts as it does in production. (The React
+    // block renders via a `ReactNodeViewRenderer` portal, so `editor.mount`
+    // isn't enough to get the preview into the DOM.)
+    root = createRoot(div);
+    flushSync(() => {
+      root.render();
+    });
+  });
+
+  afterEach(() => {
+    root.unmount();
+    editor._tiptapEditor.destroy();
+    editor = undefined as any;
+    div.remove();
+  });
+
+  /** Yields to the event loop so store-driven React re-renders can flush. */
+  function flush() {
+    return new Promise((resolve) => setTimeout(resolve, 0));
+  }
+
+  /** Replaces the document and waits for the React node views to render. */
+  async function setup(blocks: any[]) {
+    editor.replaceBlocks(editor.document, blocks);
+    // The preview is rendered asynchronously by React, so wait for it before
+    // reading its DOM.
+    await flush();
+  }
+
+  /** The preview-with-source-popup root, which holds `data-open`. */
+  function previewRoot(blockId: string): HTMLElement {
+    return div.querySelector(
+      `.bn-block[data-id="${blockId}"] .bn-preview-with-source-popup`,
+    ) as HTMLElement;
+  }
+
+  /** Whether the source popup is open (the preview is being edited). */
+  function isPopupOpen(blockId: string): boolean {
+    return previewRoot(blockId)?.getAttribute("data-open") === "true";
+  }
+
+  /** Dispatches a keydown as if the caret were in the block's (possibly
+   * hidden) source. Returns whether the default was prevented.
+   *
+   * Dispatched on the ProseMirror DOM rather than the preview element:
+   * ProseMirror ignores keydowns originating from the `contentEditable=false`
+   * preview region, so its keymap (Enter/Escape/arrows) wouldn't fire there.
+   * The handlers key off the selection (set via `setTextCursorPosition`), not
+   * the event target, so this matches a real caret in the source. */
+  function pressKey(key: string, init: KeyboardEventInit = {}): boolean {
+    const event = new KeyboardEvent("keydown", {
+      key,
+      bubbles: true,
+      cancelable: true,
+      ...init,
+    });
+    editor.prosemirrorView!.dom.dispatchEvent(event);
+    return event.defaultPrevented;
+  }
+
+  describe("with adjacent paragraphs", () => {
+    beforeEach(async () => {
+      await setup([
+        { id: "before", type: "paragraph", content: "before" },
+        { id: "math", type: "mathBlock", content: "a^2" },
+        { id: "after", type: "paragraph", content: "after" },
+      ]);
+      editor.setTextCursorPosition("math", "start");
+    });
+
+    it("Enter opens the source popup, keeping the caret in the source", async () => {
+      expect(isPopupOpen("math")).toBe(false);
+
+      expect(pressKey("Enter")).toBe(true);
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(true);
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+    });
+
+    it("Enter again closes the source popup", async () => {
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+
+      expect(pressKey("Enter")).toBe(true);
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(false);
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+    });
+
+    it("Enter commits without inserting a line break", async () => {
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+
+      // Math uses `hardBreakShortcut: "shift+enter"`, so unlike the diagram
+      // block, a plain Enter closes the popup rather than extending the source
+      // with a newline (that needs Shift+Enter - see the next test).
+      pressKey("Enter");
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(false);
+      expect(editor.getBlock("math")!.content).toEqual([
+        { type: "text", text: "a^2", styles: {} },
+      ]);
+    });
+
+    it("Shift-Enter inserts a line break, keeping the popup open", async () => {
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+
+      // Shift+Enter extends the plain-text source with a literal newline
+      // (not a `hardBreak` node, which "plain" content can't hold) instead of
+      // committing, so multi-line LaTeX is possible.
+      pressKey("Enter", { shiftKey: true });
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(true);
+      expect(editor.getBlock("math")!.content).toEqual([
+        { type: "text", text: "a^2\n", styles: {} },
+      ]);
+    });
+
+    it("Escape closes the source popup while editing", async () => {
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+
+      expect(pressKey("Escape")).toBe(true);
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(false);
+    });
+
+    it("Escape leaves an already-closed popup closed", async () => {
+      expect(isPopupOpen("math")).toBe(false);
+
+      // Defers to the default; our handler doesn't touch the popup state.
+      pressKey("Escape");
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(false);
+    });
+
+    it("ArrowRight while the popup is hidden moves to the next block", () => {
+      expect(pressKey("ArrowRight")).toBe(true);
+
+      expect(editor.getTextCursorPosition().block.id).toBe("after");
+    });
+
+    it("ArrowLeft while the popup is hidden moves to the previous block", () => {
+      expect(pressKey("ArrowLeft")).toBe(true);
+
+      expect(editor.getTextCursorPosition().block.id).toBe("before");
+    });
+
+    it("ArrowRight with Ctrl/Cmd held defers to the default (no block jump)", () => {
+      // A modifier turns the arrow into a shortcut (e.g. word/line navigation),
+      // so we don't hijack it to move between blocks.
+      expect(pressKey("ArrowRight", { ctrlKey: true })).toBe(false);
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+
+      expect(pressKey("ArrowRight", { metaKey: true })).toBe(false);
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+    });
+
+    it("ArrowRight while editing defers to the default (navigates the source)", async () => {
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+
+      // The arrow isn't hijacked: we stay in the math block with the popup open.
+      pressKey("ArrowRight");
+      await flush();
+
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+      expect(isPopupOpen("math")).toBe(true);
+    });
+
+    it("blocks character input while the popup is closed", () => {
+      expect(isPopupOpen("math")).toBe(false);
+
+      // The source is hidden, so the keystroke is swallowed (prevented) rather
+      // than silently editing the source the user can't see.
+      expect(pressKey("a")).toBe(true);
+    });
+
+    it("defers character input to the default while the popup is open", async () => {
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+
+      // The source is visible, so we don't swallow the key - ProseMirror gets to
+      // handle it as normal text input.
+      expect(pressKey("a")).toBe(false);
+    });
+
+    it("Backspace deletes the whole block while the popup is closed", () => {
+      expect(isPopupOpen("math")).toBe(false);
+
+      // The source is hidden, so Backspace removes the whole block rather than
+      // editing the source the user can't see.
+      expect(pressKey("Backspace")).toBe(true);
+      expect(editor.document.some((block) => block.id === "math")).toBe(false);
+    });
+
+    it("Delete deletes the whole block while the popup is closed", () => {
+      expect(isPopupOpen("math")).toBe(false);
+
+      expect(pressKey("Delete")).toBe(true);
+      expect(editor.document.some((block) => block.id === "math")).toBe(false);
+    });
+
+    it("blocks indent keys while the popup is closed", () => {
+      expect(isPopupOpen("math")).toBe(false);
+
+      // Tab edits the hidden source, so it's swallowed.
+      expect(pressKey("Tab")).toBe(true);
+    });
+
+    it("defers Ctrl/Cmd shortcuts to the default while the popup is closed", () => {
+      expect(isPopupOpen("math")).toBe(false);
+
+      // Single-character keys are only blocked when no Ctrl/Cmd is held, so
+      // shortcuts pass through - keeping copy/select-all/find working.
+      // (Cut/paste also pass through; that's a known limitation.)
+      expect(pressKey("c", { ctrlKey: true })).toBe(false);
+      expect(pressKey("a", { ctrlKey: true })).toBe(false);
+      expect(pressKey("f", { ctrlKey: true })).toBe(false);
+      expect(pressKey("v", { metaKey: true })).toBe(false);
+    });
+
+    it("defers deletion keys to the default while the popup is open", async () => {
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+
+      // The source is visible, so deletion is allowed through to ProseMirror.
+      expect(pressKey("Backspace")).toBe(false);
+    });
+  });
+
+  describe("at the document edges", () => {
+    it("ArrowLeft with no previous block defers to the default", async () => {
+      await setup([
+        { id: "math", type: "mathBlock", content: "a^2" },
+        { id: "after", type: "paragraph", content: "after" },
+      ]);
+      editor.setTextCursorPosition("math", "start");
+
+      // No previous block to jump to, so the arrow isn't hijacked.
+      pressKey("ArrowLeft");
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+    });
+
+    it("ArrowRight with no next block defers to the default", async () => {
+      await setup([
+        { id: "before", type: "paragraph", content: "before" },
+        { id: "math", type: "mathBlock", content: "a^2" },
+      ]);
+      editor.setTextCursorPosition("math", "start");
+
+      // No next block to jump to, so the arrow isn't hijacked.
+      pressKey("ArrowRight");
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+    });
+  });
+
+  describe("clicking the preview", () => {
+    beforeEach(async () => {
+      await setup([
+        { id: "before", type: "paragraph", content: "before" },
+        { id: "math", type: "mathBlock", content: "a^2" },
+      ]);
+      editor.setTextCursorPosition("before", "start");
+    });
+
+    it("opens the popup and places the cursor at the source end", async () => {
+      const preview = div.querySelector(
+        `.bn-block[data-id="math"] .bn-preview-container`,
+      ) as HTMLElement;
+
+      // The popup opens on the click; the mousedown is dispatched too for a
+      // realistic event sequence.
+      preview.dispatchEvent(
+        new MouseEvent("mousedown", { bubbles: true, cancelable: true }),
+      );
+      preview.dispatchEvent(
+        new MouseEvent("click", { bubbles: true, cancelable: true }),
+      );
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(true);
+      expect(editor.getTextCursorPosition().block.id).toBe("math");
+      // The cursor lands at the end of the source (after "a^2").
+      expect(editor.prosemirrorView!.state.selection.$from.parentOffset).toBe(
+        3,
+      );
+    });
+  });
+
+  describe("clicking the OK button", () => {
+    beforeEach(async () => {
+      await setup([
+        { id: "before", type: "paragraph", content: "before" },
+        { id: "math", type: "mathBlock", content: "a^2" },
+      ]);
+      editor.setTextCursorPosition("math", "start");
+      // Open the popup so the OK button has something to close.
+      pressKey("Enter");
+      await flush();
+      expect(isPopupOpen("math")).toBe(true);
+    });
+
+    it("closes the popup", async () => {
+      const okButton = div.querySelector(
+        `.bn-block[data-id="math"] .bn-code-block-source-popup-ok-button`,
+      ) as HTMLElement;
+
+      okButton.dispatchEvent(
+        new MouseEvent("mousedown", { bubbles: true, cancelable: true }),
+      );
+      okButton.dispatchEvent(
+        new MouseEvent("click", { bubbles: true, cancelable: true }),
+      );
+      await flush();
+
+      expect(isPopupOpen("math")).toBe(false);
+    });
+  });
+});
diff --git a/packages/math-block/src/block/createReactMathBlockSpec.tsx b/packages/math-block/src/block/createReactMathBlockSpec.tsx
new file mode 100644
index 0000000000..c8cb18c33d
--- /dev/null
+++ b/packages/math-block/src/block/createReactMathBlockSpec.tsx
@@ -0,0 +1,40 @@
+import { createBlockConfig } from "@blocknote/core";
+import { createReactBlockSpec } from "@blocknote/react";
+
+import { MathBlockInputRulesExtension } from "./helpers/extensions/MathBlockInputRulesExtension.js";
+import {
+  parseBlockMathMLElement,
+  parseBlockMathMLContent,
+} from "./helpers/parse/parseBlockMathMLElement.js";
+import { MathBlockPreviewWithPopup } from "./helpers/render/MathBlockPreviewWithPopup.js";
+import { BlockMathMLElement } from "./helpers/toExternalHTML/BlockMathMLElement.js";
+
+export const createMathBlockConfig = createBlockConfig(
+  () =>
+    ({
+      type: "mathBlock" as const,
+      propSchema: {},
+      content: "plain" as const,
+    }) as const,
+);
+
+export type MathBlockConfig = ReturnType;
+
+export const createReactMathBlockSpec = createReactBlockSpec(
+  createMathBlockConfig,
+  {
+    meta: {
+      code: true,
+      defining: true,
+      isolating: false,
+      highlight: () => "latex",
+      hasPreview: true,
+      hardBreakShortcut: "shift+enter",
+    },
+    parse: parseBlockMathMLElement,
+    parseContent: parseBlockMathMLContent,
+    render: MathBlockPreviewWithPopup,
+    toExternalHTML: BlockMathMLElement,
+  },
+  [MathBlockInputRulesExtension],
+);
diff --git a/packages/math-block/src/block/getMathBlockTypeSelectItems.ts b/packages/math-block/src/block/getMathBlockTypeSelectItems.ts
new file mode 100644
index 0000000000..1d3578215d
--- /dev/null
+++ b/packages/math-block/src/block/getMathBlockTypeSelectItems.ts
@@ -0,0 +1,23 @@
+import { BlockNoteEditor } from "@blocknote/core";
+import { BlockTypeSelectItem } from "@blocknote/react";
+import { TbMathFunction } from "react-icons/tb";
+
+import { getMathDictionary } from "../i18n/dictionary.js";
+
+/**
+ * Block type select item for the Math block, for use with the formatting
+ * toolbar's `BlockTypeSelect` (spread into its `items` alongside the defaults).
+ * The Math block lives in an optional package, so it isn't part of the default
+ * items - this lets consumers opt it in. The name comes from the editor's
+ * math dictionary, like the default items' names come from its main
+ * dictionary.
+ */
+export const getMathBlockTypeSelectItems = (
+  editor: BlockNoteEditor,
+): BlockTypeSelectItem[] => [
+  {
+    name: getMathDictionary(editor).block_type_select.name,
+    type: "mathBlock",
+    icon: TbMathFunction,
+  },
+];
diff --git a/packages/math-block/src/block/helpers/extensions/MathBlockInputRulesExtension.ts b/packages/math-block/src/block/helpers/extensions/MathBlockInputRulesExtension.ts
new file mode 100644
index 0000000000..fe1efec88c
--- /dev/null
+++ b/packages/math-block/src/block/helpers/extensions/MathBlockInputRulesExtension.ts
@@ -0,0 +1,24 @@
+import { createExtension } from "@blocknote/core";
+
+/**
+ * Converts the current block into a math block when a LaTeX display-math
+ * delimiter is typed at its start:
+ * - `$$ ` (TeX display math)
+ * - `\[ ` (LaTeX display math)
+ *
+ * The matched delimiter is removed and the block is replaced with an empty
+ * math block, ready for the LaTeX source to be typed in.
+ */
+export const MathBlockInputRulesExtension = createExtension({
+  key: "math-block-input-rules",
+  inputRules: [
+    {
+      find: /^\$\$\s$/,
+      replace: () => ({ type: "mathBlock", props: {}, content: [] }),
+    },
+    {
+      find: /^\\\[\s$/,
+      replace: () => ({ type: "mathBlock", props: {}, content: [] }),
+    },
+  ],
+});
diff --git a/packages/math-block/src/block/helpers/extensions/index.ts b/packages/math-block/src/block/helpers/extensions/index.ts
new file mode 100644
index 0000000000..84c9f71ba3
--- /dev/null
+++ b/packages/math-block/src/block/helpers/extensions/index.ts
@@ -0,0 +1 @@
+export * from "./MathBlockInputRulesExtension.js";
diff --git a/packages/math-block/src/block/helpers/index.ts b/packages/math-block/src/block/helpers/index.ts
new file mode 100644
index 0000000000..03c47b6db5
--- /dev/null
+++ b/packages/math-block/src/block/helpers/index.ts
@@ -0,0 +1,4 @@
+export * from "./extensions/index.js";
+export * from "./parse/index.js";
+export * from "./render/index.js";
+export * from "./toExternalHTML/index.js";
diff --git a/packages/math-block/src/block/helpers/parse/index.ts b/packages/math-block/src/block/helpers/parse/index.ts
new file mode 100644
index 0000000000..257168fb3e
--- /dev/null
+++ b/packages/math-block/src/block/helpers/parse/index.ts
@@ -0,0 +1 @@
+export * from "./parseBlockMathMLElement.js";
diff --git a/packages/math-block/src/block/helpers/parse/parseBlockMathMLElement.ts b/packages/math-block/src/block/helpers/parse/parseBlockMathMLElement.ts
new file mode 100644
index 0000000000..4bc4b51ac2
--- /dev/null
+++ b/packages/math-block/src/block/helpers/parse/parseBlockMathMLElement.ts
@@ -0,0 +1,27 @@
+import { Fragment, type Schema } from "prosemirror-model";
+
+export const parseBlockMathMLElement = (el: HTMLElement) =>
+  el.nodeName.toLowerCase() === "math" && el.getAttribute("display") === "block"
+    ? {}
+    : undefined;
+
+export const parseBlockMathMLContent = ({
+  el,
+  schema,
+}: {
+  el: HTMLElement;
+  schema: Schema;
+}) => {
+  const annotations = Array.from(el.getElementsByTagName("annotation"));
+  const texAnnotation = annotations.find(
+    (annotation) => annotation.getAttribute("encoding") === "application/x-tex",
+  );
+
+  const latex = texAnnotation?.textContent?.trim();
+
+  if (!latex) {
+    return undefined;
+  }
+
+  return Fragment.from(schema.text(latex));
+};
diff --git a/packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx b/packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx
new file mode 100644
index 0000000000..4b932b392b
--- /dev/null
+++ b/packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx
@@ -0,0 +1,50 @@
+import {
+  PreviewPlaceholder,
+  ReactCustomBlockRenderProps,
+  SourceBlockWithPreview,
+} from "@blocknote/react";
+import { TbMathFunction } from "react-icons/tb";
+
+import { MathBlockConfig } from "../../createReactMathBlockSpec.js";
+import { plainContentToString } from "@blocknote/core";
+import { useLatexToMathMLString } from "../../../helpers/render/useLatexToMathML.js";
+import { getMathDictionary } from "../../../i18n/dictionary.js";
+
+export const MathBlockPreviewWithPopup = (
+  props: ReactCustomBlockRenderProps,
+) => {
+  const source = plainContentToString(props.block.content).trim();
+  const { mathMLString, error } = useLatexToMathMLString(source);
+  const dict = getMathDictionary(props.editor).block;
+
+  return (
+    
+        ) : undefined
+      }
+      error={error}
+      errorPreview={
+        }
+          text={dict.preview_error_text}
+        />
+      }
+      emptySourcePlaceholder={
+        }
+          text={dict.add_source_text}
+        />
+      }
+      sourcePlaceholder={dict.input_placeholder}
+    />
+  );
+};
diff --git a/packages/math-block/src/block/helpers/render/index.ts b/packages/math-block/src/block/helpers/render/index.ts
new file mode 100644
index 0000000000..9ace70ae91
--- /dev/null
+++ b/packages/math-block/src/block/helpers/render/index.ts
@@ -0,0 +1 @@
+export * from "./MathBlockPreviewWithPopup.js";
diff --git a/packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx b/packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx
new file mode 100644
index 0000000000..b421550441
--- /dev/null
+++ b/packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx
@@ -0,0 +1,34 @@
+import { ReactCustomBlockRenderProps } from "@blocknote/react";
+import type { ComponentType } from "react";
+
+import { plainContentToString } from "@blocknote/core";
+import { latexToMathMLElement } from "../../../helpers/toExternalHTML/latexToMathMLElement.js";
+import { MathBlockConfig } from "../../createReactMathBlockSpec.js";
+
+export const BlockMathMLElement = ({
+  block,
+}: ReactCustomBlockRenderProps) => {
+  const source = plainContentToString(block.content);
+  const { mathMLElement } = latexToMathMLElement(source);
+  if (!mathMLElement) {
+    return null;
+  }
+
+  // `math` isn't part of React's built-in JSX types, so we alias it to a
+  // component type to render it as a JSX element.
+  const Math = "math" as unknown as ComponentType<{
+    xmlns: string;
+    display: string;
+    alttext: string;
+    dangerouslySetInnerHTML: { __html: string };
+  }>;
+
+  return (
+    
+  );
+};
diff --git a/packages/math-block/src/block/helpers/toExternalHTML/index.ts b/packages/math-block/src/block/helpers/toExternalHTML/index.ts
new file mode 100644
index 0000000000..c7f50ed3fd
--- /dev/null
+++ b/packages/math-block/src/block/helpers/toExternalHTML/index.ts
@@ -0,0 +1 @@
+export * from "./BlockMathMLElement.js";
diff --git a/packages/math-block/src/block/index.ts b/packages/math-block/src/block/index.ts
new file mode 100644
index 0000000000..0db4fcfac2
--- /dev/null
+++ b/packages/math-block/src/block/index.ts
@@ -0,0 +1,3 @@
+export * from "./createReactMathBlockSpec.js";
+export * from "./getMathBlockTypeSelectItems.js";
+export * from "./helpers/index.js";
diff --git a/packages/math-block/src/docx-exporter/__snapshots__/withMathMappings/document.xml b/packages/math-block/src/docx-exporter/__snapshots__/withMathMappings/document.xml
new file mode 100644
index 0000000000..a5b026caeb
--- /dev/null
+++ b/packages/math-block/src/docx-exporter/__snapshots__/withMathMappings/document.xml
@@ -0,0 +1,102 @@
+
+
+    
+        
+            
+                
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                            a
+                        
+                    
+                    
+                        
+                            2
+                        
+                    
+                
+                
+                    =
+                
+                
+                    
+                        
+                    
+                    
+                    
+                        
+                            
+                                
+                            
+                            
+                                
+                                    b
+                                
+                            
+                            
+                                
+                                    2
+                                
+                            
+                        
+                        
+                            +
+                        
+                        
+                            
+                                
+                            
+                            
+                                
+                                    c
+                                
+                            
+                            
+                                
+                                    2
+                                
+                            
+                        
+                    
+                
+            
+        
+        
+            
+                Inline math: 
+            
+            
+                
+                    
+                        
+                    
+                    
+                        
+                            e
+                        
+                    
+                    
+                        
+                            
+                        
+                    
+                
+                
+                    +1=0
+                
+            
+        
+        
+            
+            
+            
+            
+        
+    
+
\ No newline at end of file
diff --git a/packages/math-block/src/docx-exporter/docxExporter.test.ts b/packages/math-block/src/docx-exporter/docxExporter.test.ts
new file mode 100644
index 0000000000..4994311ab6
--- /dev/null
+++ b/packages/math-block/src/docx-exporter/docxExporter.test.ts
@@ -0,0 +1,151 @@
+import {
+  BlockNoteSchema,
+  createPageBreakBlockSpec,
+  defaultBlockSpecs,
+} from "@blocknote/core";
+import {
+  DOCXExporter,
+  docxDefaultSchemaMappings,
+} from "@blocknote/xl-docx-exporter";
+import { testDocumentWithSourceBlocks } from "@shared/testDocument.js";
+import { testResolveFileUrl } from "@shared/util/testFileResolver.js";
+import {
+  BlobReader,
+  Entry,
+  FileEntry,
+  TextWriter,
+  ZipReader,
+} from "@zip.js/zip.js";
+import { Packer } from "docx";
+import { describe, expect, it } from "vite-plus/test";
+import xmlFormat from "xml-formatter";
+
+import { inlineMathMapping, mathBlockMapping } from "./index.js";
+
+const getZIPEntryContent = (entries: Entry[], fileName: string) => {
+  const entry = entries.find((entry) => {
+    return entry.filename === fileName && !entry.directory;
+  }) as FileEntry | undefined;
+
+  if (!entry) {
+    return "";
+  }
+
+  return entry.getData!(new TextWriter());
+};
+
+const prettify = (sourceXml: string) => {
+  // Replace random ids like r:id="rIdll8_ocxarmodcwrnsavfb"
+  return xmlFormat(sourceXml)
+    .replace(/r:id="[a-zA-Z0-9_-]*"/g, 'r:id="FAKE-ID"')
+    .replace(/ Id="[a-zA-Z0-9_-]*"/g, ' Id="FAKE-ID"');
+};
+
+describe("docx exporter mappings", () => {
+  it("should export math as native equations", { timeout: 10000 }, async () => {
+    // Assembled outside the constructor call as the schema doesn't include
+    // the math specs - like the default mappings, the math entries just
+    // map the block JSON.
+    const mappings = {
+      ...docxDefaultSchemaMappings,
+      blockMapping: {
+        ...docxDefaultSchemaMappings.blockMapping,
+        mathBlock: mathBlockMapping,
+      },
+      inlineContentMapping: {
+        ...docxDefaultSchemaMappings.inlineContentMapping,
+        math: inlineMathMapping,
+      },
+    };
+    const exporter = new DOCXExporter(
+      BlockNoteSchema.create({
+        blockSpecs: {
+          ...defaultBlockSpecs,
+          pageBreak: createPageBreakBlockSpec(),
+        },
+      }),
+      mappings,
+      { resolveFileUrl: testResolveFileUrl },
+    );
+
+    // The math block & inline math paragraph from the shared test document.
+    const doc = await exporter.toDocxJsDocument(
+      testDocumentWithSourceBlocks.filter((block) =>
+        ["math-block", "paragraph-with-inline-math"].includes(block.id),
+      ),
+      { sectionOptions: {}, documentOptions: {}, locale: "en-US" },
+    );
+
+    const blob = await Packer.toBlob(doc);
+    const zip = new ZipReader(new BlobReader(blob));
+    const entries = await zip.getEntries();
+
+    await expect(
+      prettify(await getZIPEntryContent(entries, "word/document.xml")),
+    ).toMatchFileSnapshot("__snapshots__/withMathMappings/document.xml");
+  });
+
+  it("should render error placeholders for invalid LaTeX", async () => {
+    // Assembled outside the constructor call as the schema doesn't include
+    // the math specs - like the default mappings, the math entries just map
+    // the block JSON.
+    const mappings = {
+      ...docxDefaultSchemaMappings,
+      blockMapping: {
+        ...docxDefaultSchemaMappings.blockMapping,
+        mathBlock: mathBlockMapping,
+      },
+      inlineContentMapping: {
+        ...docxDefaultSchemaMappings.inlineContentMapping,
+        math: inlineMathMapping,
+      },
+    };
+    const exporter = new DOCXExporter(
+      BlockNoteSchema.create({
+        blockSpecs: {
+          ...defaultBlockSpecs,
+          pageBreak: createPageBreakBlockSpec(),
+        },
+      }),
+      mappings,
+      { resolveFileUrl: testResolveFileUrl },
+    );
+
+    const doc = await exporter.toDocxJsDocument(
+      [
+        {
+          id: "1",
+          type: "mathBlock",
+          props: {},
+          content: [{ type: "text", text: "\\invalidcommand{", styles: {} }],
+          children: [],
+        },
+        {
+          id: "2",
+          type: "paragraph",
+          props: {},
+          content: [
+            { type: "text", text: "Broken: ", styles: {} },
+            { type: "math", props: {}, content: "\\invalidcommand{" },
+          ],
+          children: [],
+        },
+      ] as any,
+      {
+        sectionOptions: {},
+        documentOptions: {},
+        locale: "en-US",
+      },
+    );
+
+    const blob = await Packer.toBlob(doc);
+    const zip = new ZipReader(new BlobReader(blob));
+    const entries = await zip.getEntries();
+    const documentXML = await getZIPEntryContent(entries, "word/document.xml");
+
+    // Mirrors the editor's error placeholder rather than dumping the LaTeX
+    // source on readers - once for the block, once for the inline math.
+    expect(documentXML.match(/Invalid formula/g)).toHaveLength(2);
+    expect(documentXML).not.toContain("m:oMath");
+  });
+});
diff --git a/packages/math-block/src/docx-exporter/index.ts b/packages/math-block/src/docx-exporter/index.ts
new file mode 100644
index 0000000000..291ca88690
--- /dev/null
+++ b/packages/math-block/src/docx-exporter/index.ts
@@ -0,0 +1,129 @@
+import type {
+  BlockConfig,
+  BlockFromConfigNoChildren,
+  Exporter,
+} from "@blocknote/core";
+import { plainContentToString } from "@blocknote/core";
+import { AlignmentType, ImportedXmlComponent, Paragraph, TextRun } from "docx";
+import { mml2omml } from "mathml2omml";
+
+import { latexToMathML } from "../exporterHelpers/latexToMathML.js";
+import { getMathExporterDictionary } from "../i18n/dictionary.js";
+
+type MathBlock = BlockFromConfigNoChildren<
+  BlockConfig<"mathBlock", {}, "plain">,
+  any,
+  any
+>;
+
+type InlineMath = { type: "math"; content: string };
+
+// Converts LaTeX to a native Word equation (OMML): KaTeX renders the LaTeX
+// to MathML, which is then converted to OMML. Invalid LaTeX comes back as a
+// typed error, for the mappings to render as a placeholder.
+function latexToDocxEquation(
+  latex: string,
+  inline: boolean,
+): { error?: undefined; equation: ImportedXmlComponent } | { error: string } {
+  const mathML = latexToMathML(latex, inline);
+  if (mathML.error !== undefined) {
+    return { error: mathML.error };
+  }
+
+  // `fromXmlString` parses the XML *document*, returning a nameless wrapper
+  // component around the `m:oMath` root element - unwrap it, or it would
+  // serialize as an (invalid) `` element.
+  const imported = ImportedXmlComponent.fromXmlString(
+    mml2omml(mathML.mathML),
+  ) as any;
+  return { equation: imported.root[0] as ImportedXmlComponent };
+}
+
+// Mirrors the editor, which shows the error state in the preview
+// placeholder, identifying the formula by its source. The parser's message
+// is deliberately NOT rendered: it's authoring detail (and untranslated
+// English) - the editor is where the author sees and fixes it.
+function errorText(
+  exporter: Exporter,
+  source: string,
+) {
+  return new TextRun({
+    text: getMathExporterDictionary(exporter).invalid_formula(source),
+    italics: true,
+    color: "999999",
+  });
+}
+
+/**
+ * DOCX block mapping for `@blocknote/math-block` that renders math blocks as
+ * native (editable) Word equations. Invalid LaTeX renders an error
+ * placeholder (mirroring the editor):
+ *
+ * ```ts
+ * import { mathBlockMapping } from "@blocknote/math-block/docx-exporter";
+ *
+ * new DOCXExporter(schema, {
+ *   ...docxDefaultSchemaMappings,
+ *   blockMapping: {
+ *     ...docxDefaultSchemaMappings.blockMapping,
+ *     mathBlock: mathBlockMapping,
+ *   },
+ * });
+ * ```
+ */
+export function mathBlockMapping(
+  block: MathBlock,
+  exporter: Exporter,
+) {
+  const source = plainContentToString(block.content);
+  if (!source.trim()) {
+    return new Paragraph({});
+  }
+
+  const result = latexToDocxEquation(source, false);
+  if (result.error !== undefined) {
+    return new Paragraph({
+      alignment: AlignmentType.CENTER,
+      children: [errorText(exporter, source)],
+    });
+  }
+
+  return new Paragraph({
+    alignment: AlignmentType.CENTER,
+    children: [result.equation as any],
+  });
+}
+
+/**
+ * DOCX inline content mapping for `@blocknote/math-block` that renders
+ * inline math as native (editable) Word equations. Invalid LaTeX renders an
+ * error placeholder (mirroring the editor):
+ *
+ * ```ts
+ * import { inlineMathMapping } from "@blocknote/math-block/docx-exporter";
+ *
+ * new DOCXExporter(schema, {
+ *   ...docxDefaultSchemaMappings,
+ *   inlineContentMapping: {
+ *     ...docxDefaultSchemaMappings.inlineContentMapping,
+ *     math: inlineMathMapping,
+ *   },
+ * });
+ * ```
+ */
+export function inlineMathMapping(
+  inlineContent: InlineMath,
+  exporter: Exporter,
+) {
+  const source = inlineContent.content;
+  if (!source.trim()) {
+    return new TextRun({ text: "" });
+  }
+
+  const result = latexToDocxEquation(source, true);
+  if (result.error !== undefined) {
+    return errorText(exporter, source);
+  }
+
+  return result.equation as any;
+}
diff --git a/packages/math-block/src/email-exporter/__snapshots__/emailExporter.test.tsx.snap b/packages/math-block/src/email-exporter/__snapshots__/emailExporter.test.tsx.snap
new file mode 100644
index 0000000000..d94f85f3be
--- /dev/null
+++ b/packages/math-block/src/email-exporter/__snapshots__/emailExporter.test.tsx.snap
@@ -0,0 +1,3 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`email exporter mappings > should export math as SVG images outside the browser > __snapshots__/emailWithMathMappings 1`] = `"
a^2 = \\sqrt{b^2 + c^2}

Inline math: e^{i\\pi} + 1 = 0

"`; diff --git a/packages/math-block/src/email-exporter/emailExporter.test.tsx b/packages/math-block/src/email-exporter/emailExporter.test.tsx new file mode 100644 index 0000000000..766e1563e7 --- /dev/null +++ b/packages/math-block/src/email-exporter/emailExporter.test.tsx @@ -0,0 +1,131 @@ +import { + BlockNoteSchema, + createPageBreakBlockSpec, + defaultBlockSpecs, +} from "@blocknote/core"; +import { + createCIDImageDelivery, + ReactEmailExporter, + reactEmailDefaultSchemaMappings, +} from "@blocknote/xl-email-exporter"; +import { testDocumentWithSourceBlocks } from "@shared/testDocument.js"; +import { describe, expect, it } from "vite-plus/test"; + +import { + createInlineMathMapping, + createMathBlockMapping, + inlineMathMapping, + mathBlockMapping, +} from "./index.js"; + +const mathTestDocument = testDocumentWithSourceBlocks.filter((block) => + ["math-block", "paragraph-with-inline-math"].includes(block.id), +); + +const createExporter = (mappings: { + math: typeof mathBlockMapping; + inlineMath: typeof inlineMathMapping; +}) => + new ReactEmailExporter( + BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + pageBreak: createPageBreakBlockSpec(), + }, + }), + { + ...reactEmailDefaultSchemaMappings, + blockMapping: { + ...reactEmailDefaultSchemaMappings.blockMapping, + mathBlock: mappings.math, + }, + inlineContentMapping: { + ...reactEmailDefaultSchemaMappings.inlineContentMapping, + math: mappings.inlineMath, + }, + } as any, + ); + +describe("email exporter mappings", () => { + it("should export math as SVG images outside the browser", async () => { + const exporter = createExporter({ + math: mathBlockMapping, + inlineMath: inlineMathMapping, + }); + + // Without a browser (or a plugged-in rasterizer), formulas are embedded + // as SVG data URLs - MathJax's SVG output is environment-independent. + const html = await exporter.toReactEmailDocument(mathTestDocument as any); + expect(html).toContain("data:image/svg+xml;base64,"); + expect(html).toMatchSnapshot("__snapshots__/emailWithMathMappings"); + }); + + it("should render error placeholders for invalid LaTeX", async () => { + const exporter = createExporter({ + math: mathBlockMapping, + inlineMath: inlineMathMapping, + }); + + const html = await exporter.toReactEmailDocument([ + { + id: "1", + type: "mathBlock", + props: {}, + content: [{ type: "text", text: "\\invalidcommand{", styles: {} }], + children: [], + }, + { + id: "2", + type: "paragraph", + props: {}, + content: [ + { type: "text", text: "Broken: ", styles: {} }, + { type: "math", props: {}, content: "\\invalidcommand{" }, + ], + children: [], + }, + ] as any); + + // Mirrors the editor's error placeholder rather than dumping the LaTeX + // source on readers - once for the block, once for the inline math. + expect(html.match(/Invalid formula/g)).toHaveLength(2); + expect(html).not.toContain(" { + const imageDelivery = createCIDImageDelivery(); + const exporter = createExporter({ + math: createMathBlockMapping({ + imageDelivery, + // A stub rasterizer, standing in for e.g. @resvg/resvg-js on a + // server. + rasterize: async (svg) => ({ + mimeType: "image/png", + data: new Uint8Array([0, 0, 0]), + width: svg.width, + height: svg.height, + }), + }), + inlineMath: createInlineMathMapping({ imageDelivery }), + }); + + const html = await exporter.toReactEmailDocument(mathTestDocument as any); + + // The body references the attachments by CID; the image contents are + // collected for the caller to attach at send time - the rasterized + // block math as PNG, the inline math as SVG. + expect(html).toContain('src="cid:math-1@blocknote"'); + expect(html).toContain('src="cid:math-2@blocknote"'); + expect(imageDelivery.attachments).toHaveLength(2); + expect(imageDelivery.attachments[0]).toEqual({ + cid: "math-1@blocknote", + filename: "math-1.png", + content: "AAAA", + encoding: "base64", + contentType: "image/png", + contentDisposition: "inline", + }); + expect(imageDelivery.attachments[1].contentType).toBe("image/svg+xml"); + expect(imageDelivery.attachments[1].filename).toBe("math-2.svg"); + }); +}); diff --git a/packages/math-block/src/email-exporter/index.tsx b/packages/math-block/src/email-exporter/index.tsx new file mode 100644 index 0000000000..63b8f19b5c --- /dev/null +++ b/packages/math-block/src/email-exporter/index.tsx @@ -0,0 +1,204 @@ +import type { + BlockConfig, + BlockFromConfigNoChildren, + Exporter, +} from "@blocknote/core"; +import { plainContentToString } from "@blocknote/core"; +import { + dataURLImageDelivery, + ReactEmailImageDelivery, +} from "@blocknote/xl-email-exporter"; +import { Img, Text } from "@react-email/components"; + +import { + latexToMathSVG, + RasterizeSVG, + rasterizeSVGInBrowser, +} from "../exporterHelpers/renderMathToImage.js"; + +type MathBlock = BlockFromConfigNoChildren< + BlockConfig<"mathBlock", {}, "plain">, + any, + any +>; + +type InlineMath = { type: "math"; content: string }; + +export { + latexToMathSVG, + rasterizeSVGInBrowser, +} from "../exporterHelpers/renderMathToImage.js"; +export type { + RasterizeSVG, + SVGExportImage, +} from "../exporterHelpers/renderMathToImage.js"; +import { getMathExporterDictionary } from "../i18n/dictionary.js"; + +type MathImageOptions = { + /** + * Rasterizes the formula SVG to a raster image. Defaults to the built-in + * canvas rasterizer in the browser; elsewhere (e.g. server-side email + * rendering), the formula is embedded as an SVG instead - pass a + * rasterizer (e.g. backed by `@resvg/resvg-js` or `sharp`) to get PNGs + * there, which more email clients display. + */ + rasterize?: RasterizeSVG; + /** + * How generated images get into the email: embedded as data URLs + * (default), or e.g. as inline `cid:` attachments via + * `createCIDImageDelivery` from `@blocknote/xl-email-exporter` - the most + * widely supported option (Gmail and Outlook block data URLs). + */ + imageDelivery?: ReactEmailImageDelivery; +}; + +// Emails render body text at 16px. +const FONT_SIZE_PIXELS = 16; + +// Mirrors the editor, which shows the error state in the preview +// placeholder, identifying the formula by its source. The parser's message +// is deliberately NOT rendered: it's authoring detail (and untranslated +// English) - the editor is where the author sees and fixes it. +function errorText( + exporter: Exporter, + source: string, +): string { + return getMathExporterDictionary(exporter).invalid_formula(source); +} + +/** + * Creates an email block mapping for `@blocknote/math-block` that renders + * math blocks as images, with the LaTeX source as the alt text. Invalid + * LaTeX renders an error placeholder (mirroring the editor). See + * {@link MathImageOptions} for how images are generated and delivered: + * + * ```ts + * import { createMathBlockMapping } from "@blocknote/math-block/email-exporter"; + * + * new ReactEmailExporter(schema, { + * ...reactEmailDefaultSchemaMappings, + * blockMapping: { + * ...reactEmailDefaultSchemaMappings.blockMapping, + * mathBlock: createMathBlockMapping({ imageDelivery }), + * }, + * }); + * ``` + */ +export function createMathBlockMapping(options?: MathImageOptions) { + return async ( + block: MathBlock, + exporter: Exporter, + ) => { + const source = plainContentToString(block.content); + if (!source.trim()) { + return ; + } + + // Rasterized when possible (see `MathImageOptions.rasterize`), embedded + // as SVG otherwise. Rasterization and delivery failures are unexpected + // and propagate (failing the export) rather than being rendered to + // readers. + const rasterize = + options?.rasterize ?? + (typeof document !== "undefined" ? rasterizeSVGInBrowser : undefined); + + const result = latexToMathSVG(source, { + inline: false, + fontSize: FONT_SIZE_PIXELS, + }); + if (result.error !== undefined) { + return ( + + {errorText(exporter, source)} + + ); + } + + const image = rasterize ? await rasterize(result.image) : result.image; + const src = (options?.imageDelivery ?? dataURLImageDelivery).deliver({ + ...image, + name: "math", + }); + + return ( + {source} + ); + }; +} + +/** + * Creates an email inline content mapping for `@blocknote/math-block` that + * renders inline math as images flowing with the text, with the LaTeX + * source as the alt text. Inline content renders synchronously, so the + * formula is always embedded as an SVG (never rasterized) - email clients + * that don't render SVG show the alt text. Invalid LaTeX renders an error + * placeholder (mirroring the editor): + * + * ```ts + * import { createInlineMathMapping } from "@blocknote/math-block/email-exporter"; + * + * new ReactEmailExporter(schema, { + * ...reactEmailDefaultSchemaMappings, + * inlineContentMapping: { + * ...reactEmailDefaultSchemaMappings.inlineContentMapping, + * math: createInlineMathMapping({ imageDelivery }), + * }, + * }); + * ``` + */ +export function createInlineMathMapping( + options?: Pick, +) { + return ( + inlineContent: InlineMath, + exporter: Exporter, + ) => { + const source = inlineContent.content; + if (!source.trim()) { + return ; + } + + const result = latexToMathSVG(source, { + inline: true, + fontSize: FONT_SIZE_PIXELS, + }); + if (result.error !== undefined) { + return ( + {errorText(exporter, source)} + ); + } + + const src = (options?.imageDelivery ?? dataURLImageDelivery).deliver({ + ...result.image, + name: "math", + }); + + return ( + {source} + ); + }; +} + +/** + * Email block mapping for `@blocknote/math-block` with the default options - + * see {@link createMathBlockMapping}. + */ +export const mathBlockMapping = createMathBlockMapping(); + +/** + * Email inline content mapping for `@blocknote/math-block` with the default + * options - see {@link createInlineMathMapping}. + */ +export const inlineMathMapping = createInlineMathMapping(); diff --git a/packages/math-block/src/exporterHelpers/latexToMathML.ts b/packages/math-block/src/exporterHelpers/latexToMathML.ts new file mode 100644 index 0000000000..d8ee60774f --- /dev/null +++ b/packages/math-block/src/exporterHelpers/latexToMathML.ts @@ -0,0 +1,40 @@ +import katex from "katex"; + +// Converts LaTeX to MathML (via KaTeX). The DOCX exporter mapping converts +// it further to OMML, the ODT mapping embeds it directly as a formula +// object. Invalid LaTeX is expected (the source is user input), so it's +// returned as a typed error - with a message safe to show to readers - +// rather than thrown. +export function latexToMathML( + latex: string, + inline: boolean, +): { error?: undefined; mathML: string } | { error: string } { + let katexOutput: string; + try { + katexOutput = katex.renderToString(latex, { + displayMode: !inline, + output: "mathml", + throwOnError: true, + }); + } catch (error) { + // The boundary that converts KaTeX's parse throw into the typed result. + // Only `ParseError`s are expected (invalid user LaTeX) - their messages + // are safe to show to readers. Anything else is a bug and propagates. + // `ParseError` is read off the same `katex` object whose + // `renderToString` just ran, so unlike a separate class import it can't + // diverge under bundler interop. + if (!(error instanceof katex.ParseError)) { + throw error; + } + return { error: error.message }; + } + + // KaTeX wraps the MathML in a `span`; callers only need the `math` + // element itself. + const mathML = katexOutput.match(//)?.[0]; + if (!mathML) { + throw new Error("No MathML found in KaTeX output"); + } + + return { mathML }; +} diff --git a/packages/math-block/src/exporterHelpers/renderMathToImage.browser.test.ts b/packages/math-block/src/exporterHelpers/renderMathToImage.browser.test.ts new file mode 100644 index 0000000000..a46fefb6de --- /dev/null +++ b/packages/math-block/src/exporterHelpers/renderMathToImage.browser.test.ts @@ -0,0 +1,33 @@ +import { exportImageToDataURL } from "@blocknote/core"; +import { decodeAndSample } from "@shared/util/browserImageTestUtil.js"; +import { describe, expect, test } from "vite-plus/test"; + +import { latexToMathSVG, rasterizeSVGInBrowser } from "./renderMathToImage.js"; + +// Browser unit tests for the browser-only rasterizer - the `RasterizeSVG` +// implementation that the (node) unit suites replace with stubs. Runs in the +// tests package's browser suite. +describe("rasterizeSVGInBrowser", () => { + test("rasterizes at the requested scale", async () => { + const result = latexToMathSVG("a^2 = \\sqrt{b^2 + c^2}", { + inline: false, + fontSize: 16, + }); + if (result.error !== undefined) { + throw new Error(`Expected a successful conversion: ${result.error}`); + } + + const raster = await rasterizeSVGInBrowser(result.image, 3); + + expect(raster.mimeType).toBe("image/png"); + // The raster keeps the display dimensions; the pixel data is scaled. + expect(raster.width).toBe(result.image.width); + expect(raster.height).toBe(result.image.height); + const { width, height, inkedPixels } = await decodeAndSample( + exportImageToDataURL(raster), + ); + expect(width).toBeGreaterThanOrEqual(Math.floor(raster.width * 3)); + expect(height).toBeGreaterThanOrEqual(Math.floor(raster.height * 3)); + expect(inkedPixels).toBeGreaterThan(0); + }); +}); diff --git a/packages/math-block/src/exporterHelpers/renderMathToImage.test.ts b/packages/math-block/src/exporterHelpers/renderMathToImage.test.ts new file mode 100644 index 0000000000..73661f8214 --- /dev/null +++ b/packages/math-block/src/exporterHelpers/renderMathToImage.test.ts @@ -0,0 +1,67 @@ +import { exportImageToDataURL } from "@blocknote/core"; +import { describe, expect, it } from "vite-plus/test"; + +import { latexToMathSVG } from "./renderMathToImage.js"; + +describe("latexToMathSVG", () => { + it("converts LaTeX to a sized SVG image", () => { + const result = latexToMathSVG("e^{i\\pi} + 1 = 0", { + inline: true, + fontSize: 16, + }); + + if (result.error !== undefined) { + throw new Error(`Expected a successful conversion: ${result.error}`); + } + expect(result.image.mimeType).toBe("image/svg+xml"); + expect(result.image.width).toBeGreaterThan(0); + expect(result.image.height).toBeGreaterThan(0); + expect(new TextDecoder().decode(result.image.data)).toMatch(/^ { + const result = latexToMathSVG("\\invalidcommand{", { + inline: true, + fontSize: 16, + }); + + expect(result.error).toBeDefined(); + }); + + it("sets the SVG's intrinsic dimensions to the display size", () => { + const result = latexToMathSVG("e^{i\\pi} + 1 = 0", { + inline: true, + fontSize: 16, + }); + + if (result.error !== undefined) { + throw new Error(`Expected a successful conversion: ${result.error}`); + } + const svg = new TextDecoder().decode(result.image.data); + // MathJax's `ex`-based dimensions must be replaced with explicit ones, + // or renderers fall back to a default size. + expect(svg).toContain(`width="${Math.ceil(result.image.width)}"`); + expect(svg).toContain(`height="${Math.ceil(result.image.height)}"`); + expect(svg).not.toMatch(/width="[\d.]+ex"/); + expect(svg).not.toMatch(/height="[\d.]+ex"/); + }); + + it("round-trips through exportImageToDataURL", () => { + const result = latexToMathSVG("e^{i\\pi} + 1 = 0", { + inline: true, + fontSize: 16, + }); + + if (result.error !== undefined) { + throw new Error(`Expected a successful conversion: ${result.error}`); + } + const dataURL = exportImageToDataURL(result.image); + expect(dataURL).toMatch(/^data:image\/svg\+xml;base64,/); + const bytes = Uint8Array.from(atob(dataURL.split(",")[1]), (char) => + char.charCodeAt(0), + ); + expect(new TextDecoder().decode(bytes)).toBe( + new TextDecoder().decode(result.image.data), + ); + }); +}); diff --git a/packages/math-block/src/exporterHelpers/renderMathToImage.ts b/packages/math-block/src/exporterHelpers/renderMathToImage.ts new file mode 100644 index 0000000000..bd24f76e08 --- /dev/null +++ b/packages/math-block/src/exporterHelpers/renderMathToImage.ts @@ -0,0 +1,209 @@ +import type { ExportImage } from "@blocknote/core"; +import { liteAdaptor } from "mathjax-full/js/adaptors/liteAdaptor.js"; +import { RegisterHTMLHandler } from "mathjax-full/js/handlers/html.js"; +import { TeX } from "mathjax-full/js/input/tex.js"; +import TexError from "mathjax-full/js/input/tex/TexError.js"; +import { mathjax } from "mathjax-full/js/mathjax.js"; + +// `mathjax-full` ships CommonJS, and depending on the consumer's bundler +// interop the default import above is either the class itself or a +// `{ default: class }` namespace object (observed with Vite serving the +// package from source). Resolve whichever is the constructor - using the +// namespace directly makes `instanceof` throw "Right-hand side of +// 'instanceof' is not callable" on the first invalid formula. +function isTexError(error: unknown): error is InstanceType { + const texErrorClass: unknown = (TexError as any).default ?? TexError; + return typeof texErrorClass === "function" && error instanceof texErrorClass; +} +import { SVG } from "mathjax-full/js/output/svg.js"; + +// Registers the TeX packages listed in `TEX_PACKAGES` below - a curated set +// with roughly KaTeX's coverage (what the editor itself renders), rather +// than `AllPackages`, which would pull every package (mhchem, physics, +// bussproofs, ...) into the bundle. +import "mathjax-full/js/input/tex/ams/AmsConfiguration.js"; +import "mathjax-full/js/input/tex/boldsymbol/BoldsymbolConfiguration.js"; +import "mathjax-full/js/input/tex/braket/BraketConfiguration.js"; +import "mathjax-full/js/input/tex/cancel/CancelConfiguration.js"; +import "mathjax-full/js/input/tex/color/ColorConfiguration.js"; +import "mathjax-full/js/input/tex/mathtools/MathtoolsConfiguration.js"; +import "mathjax-full/js/input/tex/newcommand/NewcommandConfiguration.js"; +import "mathjax-full/js/input/tex/noundefined/NoUndefinedConfiguration.js"; +import "mathjax-full/js/input/tex/textmacros/TextMacrosConfiguration.js"; +import "mathjax-full/js/input/tex/unicode/UnicodeConfiguration.js"; + +const TEX_PACKAGES = [ + "base", + "ams", + "boldsymbol", + "braket", + "cancel", + "color", + "mathtools", + "newcommand", + "noundefined", + "textmacros", + "unicode", +]; + +// MathJax (rather than KaTeX, which the math block itself renders with) is +// used for image-based export: its SVG output is self-contained paths, so it +// rasterizes without needing the KaTeX webfonts. `mathjax-full` is an +// optional peer dependency - when exporting math to PDF it's already +// installed transitively via `@react-pdf/math`. +let mathDocument: ReturnType | undefined; +let documentAdaptor: ReturnType | undefined; + +function getMathDocument() { + if (!mathDocument || !documentAdaptor) { + documentAdaptor = liteAdaptor(); + RegisterHTMLHandler(documentAdaptor); + mathDocument = mathjax.document("", { + InputJax: new TeX({ + packages: TEX_PACKAGES, + // MathJax renders TeX errors as error text by default - throw + // instead, so the conversion boundary in `latexToMathSVG` can return + // them as typed errors. + formatError: (_jax: unknown, err: TexError) => { + throw err; + }, + }), + OutputJax: new SVG({ fontCache: "none" }), + }); + } + return { mathDocument, documentAdaptor }; +} + +/** + * An {@link ExportImage} known to hold SVG markup - what + * {@link latexToMathSVG} produces and rasterizers consume, so passing a + * raster image to a rasterizer is a compile-time error. + */ +export type SVGExportImage = ExportImage & { mimeType: "image/svg+xml" }; + +// The ex height (x-height) of MathJax's font, as a fraction of its em size - +// MathJax sizes its SVG output in `ex` units, and this converts them to the +// target's units via the surrounding font size. +const EX_PER_EM = 0.442; + +/** + * Converts LaTeX to a self-contained {@link SVGExportImage} (via MathJax). + * Synchronous and environment-independent. Invalid LaTeX is expected (the + * source is user input), so it's returned as a typed error - with a message + * safe to show to readers - rather than thrown. + * + * The image's `width`/`height` are display dimensions, derived from + * `fontSize` - the surrounding font's size in the target's units (points, + * CSS pixels, ...) - and also set as the SVG's intrinsic dimensions, so + * renderers display it at the right size. + */ +export function latexToMathSVG( + latex: string, + options: { inline: boolean; fontSize: number }, +): { error?: undefined; image: SVGExportImage } | { error: string } { + const { mathDocument, documentAdaptor } = getMathDocument(); + + let node: unknown; + try { + node = mathDocument.convert(latex, { display: !options.inline }); + } catch (error) { + // The boundary that converts MathJax's TeX-error throw (see + // `formatError` above) into the typed result. Only `TexError`s are + // expected (invalid user LaTeX) - anything else is a bug and propagates. + if (!isTexError(error)) { + throw error; + } + return { error: error.message }; + } + + // The conversion returns an `mjx-container` wrapper; only the `svg` + // element itself is needed. + const svgNode = documentAdaptor.firstChild(node as any) as any; + const widthEx = parseFloat(documentAdaptor.getAttribute(svgNode, "width")); + const heightEx = parseFloat(documentAdaptor.getAttribute(svgNode, "height")); + if ( + documentAdaptor.kind(svgNode) !== "svg" || + isNaN(widthEx) || + isNaN(heightEx) + ) { + throw new Error("No SVG found in MathJax output"); + } + + // MathJax sizes the SVG in `ex` units; replace them with explicit pixel + // dimensions, or renderers fall back to a default size. + const width = widthEx * options.fontSize * EX_PER_EM; + const height = heightEx * options.fontSize * EX_PER_EM; + documentAdaptor.setAttribute(svgNode, "width", Math.ceil(width)); + documentAdaptor.setAttribute(svgNode, "height", Math.ceil(height)); + + return { + image: { + mimeType: "image/svg+xml", + data: new TextEncoder().encode(documentAdaptor.outerHTML(svgNode)), + width, + height, + }, + }; +} + +/** + * Rasterizes an {@link SVGExportImage} (from {@link latexToMathSVG}) to a + * raster image - rendered above its display size so it stays sharp in the + * exported document, at a scale of the implementation's choosing; the + * returned image keeps the display dimensions. The default implementation + * ({@link rasterizeSVGInBrowser}) needs a browser; exporters running + * elsewhere plug in their own (e.g. backed by `@resvg/resvg-js`'s `fitTo` + * zoom or `sharp`'s density). + */ +export type RasterizeSVG = (svg: SVGExportImage) => Promise; + +const DEFAULT_RASTER_SCALE = 2; + +/** + * Rasterizes an {@link SVGExportImage} to a PNG via a canvas, at `scale` + * times its display size. The screen's device pixel ratio is deliberately + * not consulted for the scale, since the output goes into documents, not + * onto the current screen. Browser-only. + */ +export async function rasterizeSVGInBrowser( + svg: SVGExportImage, + scale: number = DEFAULT_RASTER_SCALE, +): Promise { + const width = Math.max(1, Math.ceil(svg.width * scale)); + const height = Math.max(1, Math.ceil(svg.height * scale)); + + // The scale goes into the SVG's intrinsic dimensions (rather than only + // the canvas): browsers rasterize an SVG image at its intrinsic size and + // upscale the bitmap when drawn larger, which would blur the output. + const svgElement = new DOMParser().parseFromString( + new TextDecoder().decode(svg.data), + "image/svg+xml", + ).documentElement; + svgElement.setAttribute("width", String(width)); + svgElement.setAttribute("height", String(height)); + + const image = new Image(); + image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent( + new XMLSerializer().serializeToString(svgElement), + )}`; + await image.decode(); + + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + canvas.getContext("2d")!.drawImage(image, 0, 0, canvas.width, canvas.height); + + const blob = await new Promise((resolve) => + canvas.toBlob(resolve, "image/png"), + ); + if (!blob) { + throw new Error("Canvas produced no PNG data"); + } + + return { + mimeType: "image/png", + data: new Uint8Array(await blob.arrayBuffer()), + width: svg.width, + height: svg.height, + }; +} diff --git a/packages/math-block/src/getMathSlashMenuItems.tsx b/packages/math-block/src/getMathSlashMenuItems.tsx new file mode 100644 index 0000000000..21992b794b --- /dev/null +++ b/packages/math-block/src/getMathSlashMenuItems.tsx @@ -0,0 +1,74 @@ +import { + BlockNoteEditor, + SourceBlockWithPreviewExtension, +} from "@blocknote/core"; +import { insertOrUpdateBlockForSlashMenu } from "@blocknote/core/extensions"; +import { DefaultReactSuggestionItem } from "@blocknote/react"; +import { TextSelection } from "prosemirror-state"; +import { TbMathFunction } from "react-icons/tb"; + +import { getMathDictionary } from "./i18n/dictionary.js"; + +/** + * Slash menu items for the Math block and inline Math content, for use with + * the suggestion menu (combine with the default items via `combineByGroup`). + * The Math specs live in an optional package, so the items aren't part of the + * defaults - this lets consumers opt them in. Only items whose spec is + * actually in the editor's schema are returned. + */ +export function getMathSlashMenuItems( + editor: BlockNoteEditor, +): Omit[] { + const items: Omit[] = []; + + if ("mathBlock" in editor.schema.blockSchema) { + items.push({ + ...getMathDictionary(editor).slash_menu.math_block, + icon: , + onItemClick: () => { + const block = insertOrUpdateBlockForSlashMenu(editor, { + type: "mathBlock", + }); + // Opens the new block's source popup so the equation can be typed + // right away. + editor + .getExtension(SourceBlockWithPreviewExtension) + ?.store.setState((state) => ({ ...state, popupOpen: block.id })); + requestAnimationFrame(() => { + editor.setTextCursorPosition(block.id, "end"); + editor.focus(); + }); + }, + }); + } + + if ("math" in editor.schema.inlineContentSchema) { + items.push({ + ...getMathDictionary(editor).slash_menu.inline_math, + icon: , + onItemClick: () => { + const view = editor.prosemirrorView!; + const insertPos = view.state.selection.from; + + editor.insertInlineContent([ + { type: "math", content: "" }, + // Adds a trailing space so the cursor can leave the equation. + " ", + ]); + // Moves the cursor into the (empty) equation, which opens its source + // popup so it can be typed right away. + requestAnimationFrame(() => { + const view = editor.prosemirrorView!; + view.dispatch( + view.state.tr.setSelection( + TextSelection.create(view.state.doc, insertPos + 1), + ), + ); + editor.focus(); + }); + }, + }); + } + + return items; +} diff --git a/packages/math-block/src/helpers/index.ts b/packages/math-block/src/helpers/index.ts new file mode 100644 index 0000000000..782657090d --- /dev/null +++ b/packages/math-block/src/helpers/index.ts @@ -0,0 +1,3 @@ +export * from "./latexToHTMLString.js"; +export * from "./render/index.js"; +export * from "./toExternalHTML/index.js"; diff --git a/packages/math-block/src/helpers/latexToHTMLString.ts b/packages/math-block/src/helpers/latexToHTMLString.ts new file mode 100644 index 0000000000..c87575d51d --- /dev/null +++ b/packages/math-block/src/helpers/latexToHTMLString.ts @@ -0,0 +1,28 @@ +import katex from "katex"; +import "katex/dist/katex.min.css"; + +export const latexToHTMLString = ( + latex: string, + inline = false, + external = false, +) => { + try { + return { + htmlString: katex.renderToString(latex, { + throwOnError: true, + displayMode: !inline, + output: external ? "mathml" : "htmlAndMathml", + }), + error: undefined, + }; + } catch (error) { + return { + htmlString: katex.renderToString(latex, { + throwOnError: false, + displayMode: !inline, + output: external ? "mathml" : "htmlAndMathml", + }), + error: error instanceof Error ? error.message : String(error), + }; + } +}; diff --git a/packages/math-block/src/helpers/render/index.ts b/packages/math-block/src/helpers/render/index.ts new file mode 100644 index 0000000000..ff76ef360b --- /dev/null +++ b/packages/math-block/src/helpers/render/index.ts @@ -0,0 +1 @@ +export * from "./useLatexToMathML.js"; diff --git a/packages/math-block/src/helpers/render/useLatexToMathML.ts b/packages/math-block/src/helpers/render/useLatexToMathML.ts new file mode 100644 index 0000000000..3fd97a7496 --- /dev/null +++ b/packages/math-block/src/helpers/render/useLatexToMathML.ts @@ -0,0 +1,14 @@ +import { useRef } from "react"; + +import { latexToHTMLString } from "../latexToHTMLString.js"; + +export const useLatexToMathMLString = (latex: string, inline = false) => { + const lastValidMathMLStringRef = useRef(""); + + const { htmlString: mathMLString, error } = latexToHTMLString(latex, inline); + if (!error || lastValidMathMLStringRef.current === "") { + lastValidMathMLStringRef.current = mathMLString; + } + + return { mathMLString: lastValidMathMLStringRef.current, error }; +}; diff --git a/packages/math-block/src/helpers/toExternalHTML/index.ts b/packages/math-block/src/helpers/toExternalHTML/index.ts new file mode 100644 index 0000000000..da198d5f83 --- /dev/null +++ b/packages/math-block/src/helpers/toExternalHTML/index.ts @@ -0,0 +1 @@ +export * from "./latexToMathMLElement.js"; diff --git a/packages/math-block/src/helpers/toExternalHTML/latexToMathMLElement.ts b/packages/math-block/src/helpers/toExternalHTML/latexToMathMLElement.ts new file mode 100644 index 0000000000..0b72fe2159 --- /dev/null +++ b/packages/math-block/src/helpers/toExternalHTML/latexToMathMLElement.ts @@ -0,0 +1,16 @@ +import { latexToHTMLString } from "../latexToHTMLString.js"; + +export const latexToMathMLElement = (latex: string, inline = false) => { + const { htmlString: mathMLString, error } = latexToHTMLString( + latex, + inline, + true, + ); + + // Katex wraps the `math` element in a `span`, which we don't need. + const wrapper = document.createElement("div"); + wrapper.innerHTML = mathMLString; + const mathMLElement = wrapper.querySelector("math") as MathMLElement; + + return { mathMLElement, error }; +}; diff --git a/packages/math-block/src/i18n/dictionary.ts b/packages/math-block/src/i18n/dictionary.ts new file mode 100644 index 0000000000..88ec5dace1 --- /dev/null +++ b/packages/math-block/src/i18n/dictionary.ts @@ -0,0 +1,33 @@ +import { BlockNoteEditor, Exporter } from "@blocknote/core"; + +import { en } from "./locales/en.js"; + +export type MathDictionary = typeof en; + +/** + * Returns the Math dictionary for the editor. The Math block/inline content are + * localized by merging a `math` dictionary into the editor's dictionary (see + * the exported `locales`); when the host hasn't provided one, the bundled + * English strings are used, so the blocks work without extra setup. + */ +export function getMathDictionary( + editor: BlockNoteEditor, +): MathDictionary { + return ((editor.dictionary as any).math as MathDictionary | undefined) ?? en; +} + +/** + * Returns the Math exporter strings. Exporters are localized independently + * of an editor: the host passes a dictionary to the exporter's options + * (see `ExporterOptions.dictionary`), and the math strings are read from + * its `math` section - the same shape merged into editor dictionaries - + * falling back to the bundled English strings. + */ +export function getMathExporterDictionary( + exporter: Exporter, +): MathDictionary["exporter"] { + return ( + ((exporter.options.dictionary as any)?.math as MathDictionary | undefined) + ?.exporter ?? en.exporter + ); +} diff --git a/packages/math-block/src/i18n/locales/ar.ts b/packages/math-block/src/i18n/locales/ar.ts new file mode 100644 index 0000000000..3cf96a16bb --- /dev/null +++ b/packages/math-block/src/i18n/locales/ar.ts @@ -0,0 +1,35 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const ar: MathDictionary = { + block: { + add_source_text: "إضافة معادلة LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "معادلة غير صالحة (انقر للتعديل)", + }, + inline: { + add_source_text: "إضافة معادلة LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "معادلة غير صالحة (انقر للتعديل)", + }, + slash_menu: { + math_block: { + title: "معادلة مستقلة", + subtext: "كتلة معادلة رياضية مستقلة", + aliases: ["رياضيات", "معادلة", "صيغة", "latex"], + group: "متقدم", + }, + inline_math: { + title: "معادلة ضمن السطر", + subtext: "رموز رياضية داخل النص", + aliases: ["رياضيات", "معادلة", "صيغة", "latex"], + group: "متقدم", + }, + }, + block_type_select: { + name: "معادلة", + }, + exporter: { + invalid_formula: (source: string) => + `صيغة غير صالحة "\u2068${source}\u2069"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/de.ts b/packages/math-block/src/i18n/locales/de.ts new file mode 100644 index 0000000000..319d2679ce --- /dev/null +++ b/packages/math-block/src/i18n/locales/de.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const de: MathDictionary = { + block: { + add_source_text: "LaTeX-Gleichung hinzufügen", + input_placeholder: "E = mc^2", + preview_error_text: "Ungültige Gleichung (zum Bearbeiten klicken)", + }, + inline: { + add_source_text: "LaTeX-Gleichung hinzufügen", + input_placeholder: "E = mc^2", + preview_error_text: "Ungültige Gleichung (zum Bearbeiten klicken)", + }, + slash_menu: { + math_block: { + title: "Blockgleichung", + subtext: "Eigenständiger Gleichungsblock", + aliases: ["mathe", "latex", "formel", "gleichung"], + group: "Erweitert", + }, + inline_math: { + title: "Inline-Gleichung", + subtext: "Mathematische Symbole im Text", + aliases: ["mathe", "latex", "formel", "gleichung"], + group: "Erweitert", + }, + }, + block_type_select: { + name: "Gleichung", + }, + exporter: { + invalid_formula: (source: string) => `Ungültige Formel "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/en.ts b/packages/math-block/src/i18n/locales/en.ts new file mode 100644 index 0000000000..44f42f5a00 --- /dev/null +++ b/packages/math-block/src/i18n/locales/en.ts @@ -0,0 +1,32 @@ +export const en = { + block: { + add_source_text: "Add a LaTeX equation", + input_placeholder: "E = mc^2", + preview_error_text: "Invalid equation (click to edit)", + }, + inline: { + add_source_text: "Add a LaTeX equation", + input_placeholder: "E = mc^2", + preview_error_text: "Invalid equation (click to edit)", + }, + slash_menu: { + math_block: { + title: "Block Equation", + subtext: "Standalone math equation block", + aliases: ["math", "latex", "formula", "equation"], + group: "Advanced", + }, + inline_math: { + title: "Inline Equation", + subtext: "Math symbols in text", + aliases: ["math", "latex", "formula", "equation"], + group: "Advanced", + }, + }, + block_type_select: { + name: "Equation", + }, + exporter: { + invalid_formula: (source: string) => `Invalid formula "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/es.ts b/packages/math-block/src/i18n/locales/es.ts new file mode 100644 index 0000000000..cdaacfbbdc --- /dev/null +++ b/packages/math-block/src/i18n/locales/es.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const es: MathDictionary = { + block: { + add_source_text: "Agregar ecuación LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Ecuación no válida (haz clic para editar)", + }, + inline: { + add_source_text: "Agregar ecuación LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Ecuación no válida (haz clic para editar)", + }, + slash_menu: { + math_block: { + title: "Bloque de Ecuación", + subtext: "Bloque de ecuación matemática independiente", + aliases: ["matemáticas", "latex", "fórmula", "ecuación"], + group: "Avanzado", + }, + inline_math: { + title: "Ecuación en línea", + subtext: "Símbolos matemáticos en el texto", + aliases: ["matemáticas", "latex", "fórmula", "ecuación"], + group: "Avanzado", + }, + }, + block_type_select: { + name: "Ecuación", + }, + exporter: { + invalid_formula: (source: string) => `Fórmula no válida "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/fa.ts b/packages/math-block/src/i18n/locales/fa.ts new file mode 100644 index 0000000000..309896aa75 --- /dev/null +++ b/packages/math-block/src/i18n/locales/fa.ts @@ -0,0 +1,35 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const fa: MathDictionary = { + block: { + add_source_text: "افزودن معادله LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "معادله نامعتبر (برای ویرایش کلیک کنید)", + }, + inline: { + add_source_text: "افزودن معادله LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "معادله نامعتبر (برای ویرایش کلیک کنید)", + }, + slash_menu: { + math_block: { + title: "بلوک معادله", + subtext: "بلوک معادله ریاضی مستقل", + aliases: ["ریاضی", "فرمول", "معادله", "latex"], + group: "پیشرفته", + }, + inline_math: { + title: "معادله درون‌خطی", + subtext: "نمادهای ریاضی درون متن", + aliases: ["ریاضی", "فرمول", "معادله", "latex"], + group: "پیشرفته", + }, + }, + block_type_select: { + name: "معادله", + }, + exporter: { + invalid_formula: (source: string) => + `فرمول نامعتبر "\u2068${source}\u2069"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/fr.ts b/packages/math-block/src/i18n/locales/fr.ts new file mode 100644 index 0000000000..005eaf3d6b --- /dev/null +++ b/packages/math-block/src/i18n/locales/fr.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const fr: MathDictionary = { + block: { + add_source_text: "Ajouter une équation LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Équation non valide (cliquez pour modifier)", + }, + inline: { + add_source_text: "Ajouter une équation LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Équation non valide (cliquez pour modifier)", + }, + slash_menu: { + math_block: { + title: "Bloc d'équation", + subtext: "Bloc d'équation mathématique autonome", + aliases: ["maths", "latex", "formule", "équation"], + group: "Avancé", + }, + inline_math: { + title: "Équation en ligne", + subtext: "Symboles mathématiques dans le texte", + aliases: ["maths", "latex", "formule", "équation"], + group: "Avancé", + }, + }, + block_type_select: { + name: "Équation", + }, + exporter: { + invalid_formula: (source: string) => `Formule non valide "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/he.ts b/packages/math-block/src/i18n/locales/he.ts new file mode 100644 index 0000000000..efa5b5cb8c --- /dev/null +++ b/packages/math-block/src/i18n/locales/he.ts @@ -0,0 +1,35 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const he: MathDictionary = { + block: { + add_source_text: "הוסף משוואת LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "משוואה לא תקינה (לחץ לעריכה)", + }, + inline: { + add_source_text: "הוסף משוואת LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "משוואה לא תקינה (לחץ לעריכה)", + }, + slash_menu: { + math_block: { + title: "בלוק משוואה", + subtext: "בלוק משוואה מתמטית עצמאי", + aliases: ["מתמטיקה", "נוסחה", "משוואה", "latex"], + group: "מתקדם", + }, + inline_math: { + title: "משוואה בתוך השורה", + subtext: "סמלים מתמטיים בתוך הטקסט", + aliases: ["מתמטיקה", "נוסחה", "משוואה", "latex"], + group: "מתקדם", + }, + }, + block_type_select: { + name: "משוואה", + }, + exporter: { + invalid_formula: (source: string) => + `נוסחה לא חוקית "\u2068${source}\u2069"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/hr.ts b/packages/math-block/src/i18n/locales/hr.ts new file mode 100644 index 0000000000..26234cb776 --- /dev/null +++ b/packages/math-block/src/i18n/locales/hr.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const hr: MathDictionary = { + block: { + add_source_text: "Dodaj LaTeX jednadžbu", + input_placeholder: "E = mc^2", + preview_error_text: "Neispravna jednadžba (klikni za uređivanje)", + }, + inline: { + add_source_text: "Dodaj LaTeX jednadžbu", + input_placeholder: "E = mc^2", + preview_error_text: "Neispravna jednadžba (klikni za uređivanje)", + }, + slash_menu: { + math_block: { + title: "Jednadžba u bloku", + subtext: "Samostalni blok matematičke jednadžbe", + aliases: ["matematika", "formula", "jednadžba", "latex"], + group: "Napredno", + }, + inline_math: { + title: "Jednadžba u retku", + subtext: "Matematički simboli u tekstu", + aliases: ["matematika", "formula", "jednadžba", "latex"], + group: "Napredno", + }, + }, + block_type_select: { + name: "Jednadžba", + }, + exporter: { + invalid_formula: (source: string) => `Neispravna formula "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/index.ts b/packages/math-block/src/i18n/locales/index.ts new file mode 100644 index 0000000000..f92fca4228 --- /dev/null +++ b/packages/math-block/src/i18n/locales/index.ts @@ -0,0 +1,23 @@ +export * from "./ar.js"; +export * from "./de.js"; +export * from "./en.js"; +export * from "./es.js"; +export * from "./fa.js"; +export * from "./fr.js"; +export * from "./he.js"; +export * from "./hr.js"; +export * from "./is.js"; +export * from "./it.js"; +export * from "./ja.js"; +export * from "./ko.js"; +export * from "./nl.js"; +export * from "./no.js"; +export * from "./pl.js"; +export * from "./pt.js"; +export * from "./ru.js"; +export * from "./sk.js"; +export * from "./uk.js"; +export * from "./vi.js"; +export * from "./zh.js"; +export * from "./zh-tw.js"; +export * from "./uz.js"; diff --git a/packages/math-block/src/i18n/locales/is.ts b/packages/math-block/src/i18n/locales/is.ts new file mode 100644 index 0000000000..fe4595bad3 --- /dev/null +++ b/packages/math-block/src/i18n/locales/is.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const is: MathDictionary = { + block: { + add_source_text: "Bæta við LaTeX-jöfnu", + input_placeholder: "E = mc^2", + preview_error_text: "Ógild jafna (smelltu til að breyta)", + }, + inline: { + add_source_text: "Bæta við LaTeX-jöfnu", + input_placeholder: "E = mc^2", + preview_error_text: "Ógild jafna (smelltu til að breyta)", + }, + slash_menu: { + math_block: { + title: "Jöfnublokk", + subtext: "Sjálfstæð jöfnublokk", + aliases: ["stærðfræði", "formúla", "jafna", "latex"], + group: "Ítarlegt", + }, + inline_math: { + title: "Innfelld jafna", + subtext: "Stærðfræðitákn í texta", + aliases: ["stærðfræði", "formúla", "jafna", "latex"], + group: "Ítarlegt", + }, + }, + block_type_select: { + name: "Jafna", + }, + exporter: { + invalid_formula: (source: string) => `Ógild formúla "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/it.ts b/packages/math-block/src/i18n/locales/it.ts new file mode 100644 index 0000000000..7e3e8766f4 --- /dev/null +++ b/packages/math-block/src/i18n/locales/it.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const it: MathDictionary = { + block: { + add_source_text: "Aggiungi equazione LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Equazione non valida (clicca per modificare)", + }, + inline: { + add_source_text: "Aggiungi equazione LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Equazione non valida (clicca per modificare)", + }, + slash_menu: { + math_block: { + title: "Blocco Equazione", + subtext: "Blocco di equazione matematica indipendente", + aliases: ["matematica", "formula", "equazione", "latex"], + group: "Avanzato", + }, + inline_math: { + title: "Equazione in linea", + subtext: "Simboli matematici nel testo", + aliases: ["matematica", "formula", "equazione", "latex"], + group: "Avanzato", + }, + }, + block_type_select: { + name: "Equazione", + }, + exporter: { + invalid_formula: (source: string) => `Formula non valida "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/ja.ts b/packages/math-block/src/i18n/locales/ja.ts new file mode 100644 index 0000000000..fb45f14689 --- /dev/null +++ b/packages/math-block/src/i18n/locales/ja.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const ja: MathDictionary = { + block: { + add_source_text: "LaTeX数式を追加", + input_placeholder: "E = mc^2", + preview_error_text: "無効な数式(クリックして編集)", + }, + inline: { + add_source_text: "LaTeX数式を追加", + input_placeholder: "E = mc^2", + preview_error_text: "無効な数式(クリックして編集)", + }, + slash_menu: { + math_block: { + title: "数式ブロック", + subtext: "独立した数式ブロック", + aliases: ["数式", "すうしき", "math", "latex"], + group: "高度なブロック", + }, + inline_math: { + title: "インライン数式", + subtext: "テキスト内の数学記号", + aliases: ["数式", "すうしき", "math", "latex"], + group: "高度なブロック", + }, + }, + block_type_select: { + name: "数式", + }, + exporter: { + invalid_formula: (source: string) => `無効な数式 "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/ko.ts b/packages/math-block/src/i18n/locales/ko.ts new file mode 100644 index 0000000000..4ea265326c --- /dev/null +++ b/packages/math-block/src/i18n/locales/ko.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const ko: MathDictionary = { + block: { + add_source_text: "LaTeX 수식 추가", + input_placeholder: "E = mc^2", + preview_error_text: "잘못된 수식(클릭하여 편집)", + }, + inline: { + add_source_text: "LaTeX 수식 추가", + input_placeholder: "E = mc^2", + preview_error_text: "잘못된 수식(클릭하여 편집)", + }, + slash_menu: { + math_block: { + title: "수식 블록", + subtext: "독립된 수식 블록", + aliases: ["수식", "공식", "math", "latex"], + group: "고급", + }, + inline_math: { + title: "인라인 수식", + subtext: "텍스트 속 수학 기호", + aliases: ["수식", "공식", "math", "latex"], + group: "고급", + }, + }, + block_type_select: { + name: "수식", + }, + exporter: { + invalid_formula: (source: string) => `잘못된 수식 "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/nl.ts b/packages/math-block/src/i18n/locales/nl.ts new file mode 100644 index 0000000000..ad547a75e2 --- /dev/null +++ b/packages/math-block/src/i18n/locales/nl.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const nl: MathDictionary = { + block: { + add_source_text: "LaTeX-formule toevoegen", + input_placeholder: "E = mc^2", + preview_error_text: "Ongeldige formule (klik om te bewerken)", + }, + inline: { + add_source_text: "LaTeX-formule toevoegen", + input_placeholder: "E = mc^2", + preview_error_text: "Ongeldige formule (klik om te bewerken)", + }, + slash_menu: { + math_block: { + title: "Formuleblok", + subtext: "Losstaande wiskundige formule", + aliases: ["wiskunde", "formule", "vergelijking", "latex"], + group: "Geavanceerd", + }, + inline_math: { + title: "Inline formule", + subtext: "Wiskundige symbolen in de tekst", + aliases: ["wiskunde", "formule", "vergelijking", "latex"], + group: "Geavanceerd", + }, + }, + block_type_select: { + name: "Formule", + }, + exporter: { + invalid_formula: (source: string) => `Ongeldige formule "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/no.ts b/packages/math-block/src/i18n/locales/no.ts new file mode 100644 index 0000000000..3abc349e2d --- /dev/null +++ b/packages/math-block/src/i18n/locales/no.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const no: MathDictionary = { + block: { + add_source_text: "Legg til LaTeX-ligning", + input_placeholder: "E = mc^2", + preview_error_text: "Ugyldig ligning (klikk for å redigere)", + }, + inline: { + add_source_text: "Legg til LaTeX-ligning", + input_placeholder: "E = mc^2", + preview_error_text: "Ugyldig ligning (klikk for å redigere)", + }, + slash_menu: { + math_block: { + title: "Blokkligning", + subtext: "Frittstående ligningsblokk", + aliases: ["matte", "formel", "ligning", "latex"], + group: "Avansert", + }, + inline_math: { + title: "Innebygd ligning", + subtext: "Matematiske symboler i tekst", + aliases: ["matte", "formel", "ligning", "latex"], + group: "Avansert", + }, + }, + block_type_select: { + name: "Ligning", + }, + exporter: { + invalid_formula: (source: string) => `Ugyldig formel "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/pl.ts b/packages/math-block/src/i18n/locales/pl.ts new file mode 100644 index 0000000000..1222219735 --- /dev/null +++ b/packages/math-block/src/i18n/locales/pl.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const pl: MathDictionary = { + block: { + add_source_text: "Dodaj równanie LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Nieprawidłowe równanie (kliknij, aby edytować)", + }, + inline: { + add_source_text: "Dodaj równanie LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Nieprawidłowe równanie (kliknij, aby edytować)", + }, + slash_menu: { + math_block: { + title: "Równanie blokowe", + subtext: "Samodzielny blok równania matematycznego", + aliases: ["matematyka", "formuła", "równanie", "latex"], + group: "Zaawansowane", + }, + inline_math: { + title: "Równanie w tekście", + subtext: "Symbole matematyczne w tekście", + aliases: ["matematyka", "formuła", "równanie", "latex"], + group: "Zaawansowane", + }, + }, + block_type_select: { + name: "Równanie", + }, + exporter: { + invalid_formula: (source: string) => `Nieprawidłowa formuła "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/pt.ts b/packages/math-block/src/i18n/locales/pt.ts new file mode 100644 index 0000000000..0c693cd7e4 --- /dev/null +++ b/packages/math-block/src/i18n/locales/pt.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const pt: MathDictionary = { + block: { + add_source_text: "Adicionar equação LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Equação inválida (clique para editar)", + }, + inline: { + add_source_text: "Adicionar equação LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Equação inválida (clique para editar)", + }, + slash_menu: { + math_block: { + title: "Bloco de Equação", + subtext: "Bloco de equação matemática independente", + aliases: ["matemática", "fórmula", "equação", "latex"], + group: "Avançado", + }, + inline_math: { + title: "Equação em linha", + subtext: "Símbolos matemáticos no texto", + aliases: ["matemática", "fórmula", "equação", "latex"], + group: "Avançado", + }, + }, + block_type_select: { + name: "Equação", + }, + exporter: { + invalid_formula: (source: string) => `Fórmula inválida "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/ru.ts b/packages/math-block/src/i18n/locales/ru.ts new file mode 100644 index 0000000000..9ae7f6bfc8 --- /dev/null +++ b/packages/math-block/src/i18n/locales/ru.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const ru: MathDictionary = { + block: { + add_source_text: "Добавить формулу LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Некорректная формула (нажмите, чтобы изменить)", + }, + inline: { + add_source_text: "Добавить формулу LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Некорректная формула (нажмите, чтобы изменить)", + }, + slash_menu: { + math_block: { + title: "Блочная формула", + subtext: "Отдельный блок с математической формулой", + aliases: ["математика", "формула", "уравнение", "latex"], + group: "Продвинутый", + }, + inline_math: { + title: "Встроенная формула", + subtext: "Математические символы в тексте", + aliases: ["математика", "формула", "уравнение", "latex"], + group: "Продвинутый", + }, + }, + block_type_select: { + name: "Формула", + }, + exporter: { + invalid_formula: (source: string) => `Недопустимая формула "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/sk.ts b/packages/math-block/src/i18n/locales/sk.ts new file mode 100644 index 0000000000..3356fe2f62 --- /dev/null +++ b/packages/math-block/src/i18n/locales/sk.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const sk: MathDictionary = { + block: { + add_source_text: "Pridať rovnicu LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Neplatná rovnica (kliknutím upravíte)", + }, + inline: { + add_source_text: "Pridať rovnicu LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Neplatná rovnica (kliknutím upravíte)", + }, + slash_menu: { + math_block: { + title: "Bloková rovnica", + subtext: "Samostatný blok matematickej rovnice", + aliases: ["matematika", "vzorec", "rovnica", "latex"], + group: "Pokročilé", + }, + inline_math: { + title: "Rovnica v texte", + subtext: "Matematické symboly v texte", + aliases: ["matematika", "vzorec", "rovnica", "latex"], + group: "Pokročilé", + }, + }, + block_type_select: { + name: "Rovnica", + }, + exporter: { + invalid_formula: (source: string) => `Neplatný vzorec "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/uk.ts b/packages/math-block/src/i18n/locales/uk.ts new file mode 100644 index 0000000000..4ef1fe3b5e --- /dev/null +++ b/packages/math-block/src/i18n/locales/uk.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const uk: MathDictionary = { + block: { + add_source_text: "Додати формулу LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Некоректна формула (натисніть, щоб редагувати)", + }, + inline: { + add_source_text: "Додати формулу LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Некоректна формула (натисніть, щоб редагувати)", + }, + slash_menu: { + math_block: { + title: "Блокова формула", + subtext: "Окремий блок з математичною формулою", + aliases: ["математика", "формула", "рівняння", "latex"], + group: "Розширені", + }, + inline_math: { + title: "Вбудована формула", + subtext: "Математичні символи в тексті", + aliases: ["математика", "формула", "рівняння", "latex"], + group: "Розширені", + }, + }, + block_type_select: { + name: "Формула", + }, + exporter: { + invalid_formula: (source: string) => `Недійсна формула "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/uz.ts b/packages/math-block/src/i18n/locales/uz.ts new file mode 100644 index 0000000000..2da22e130f --- /dev/null +++ b/packages/math-block/src/i18n/locales/uz.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const uz: MathDictionary = { + block: { + add_source_text: "LaTeX tenglamasini qo‘shish", + input_placeholder: "E = mc^2", + preview_error_text: "Yaroqsiz tenglama (tahrirlash uchun bosing)", + }, + inline: { + add_source_text: "LaTeX tenglamasini qo‘shish", + input_placeholder: "E = mc^2", + preview_error_text: "Yaroqsiz tenglama (tahrirlash uchun bosing)", + }, + slash_menu: { + math_block: { + title: "Tenglama bloki", + subtext: "Mustaqil matematik tenglama bloki", + aliases: ["matematika", "formula", "tenglama", "latex"], + group: "Kengaytirilgan", + }, + inline_math: { + title: "Matn ichidagi tenglama", + subtext: "Matn ichidagi matematik belgilar", + aliases: ["matematika", "formula", "tenglama", "latex"], + group: "Kengaytirilgan", + }, + }, + block_type_select: { + name: "Tenglama", + }, + exporter: { + invalid_formula: (source: string) => `Yaroqsiz formula "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/vi.ts b/packages/math-block/src/i18n/locales/vi.ts new file mode 100644 index 0000000000..cbce3e0fa2 --- /dev/null +++ b/packages/math-block/src/i18n/locales/vi.ts @@ -0,0 +1,50 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const vi: MathDictionary = { + block: { + add_source_text: "Thêm phương trình LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Phương trình không hợp lệ (nhấp để chỉnh sửa)", + }, + inline: { + add_source_text: "Thêm phương trình LaTeX", + input_placeholder: "E = mc^2", + preview_error_text: "Phương trình không hợp lệ (nhấp để chỉnh sửa)", + }, + slash_menu: { + math_block: { + title: "Phương trình dạng khối", + subtext: "Khối phương trình toán học độc lập", + aliases: [ + "toán", + "công thức", + "phương trình", + "latex", + "toan", + "cong thuc", + "phuong trinh", + ], + group: "Nâng cao", + }, + inline_math: { + title: "Phương trình nội dòng", + subtext: "Ký hiệu toán học trong văn bản", + aliases: [ + "toán", + "công thức", + "phương trình", + "latex", + "toan", + "cong thuc", + "phuong trinh", + ], + group: "Nâng cao", + }, + }, + block_type_select: { + name: "Phương trình", + }, + exporter: { + invalid_formula: (source: string) => `Công thức không hợp lệ "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/zh-tw.ts b/packages/math-block/src/i18n/locales/zh-tw.ts new file mode 100644 index 0000000000..de5deb445f --- /dev/null +++ b/packages/math-block/src/i18n/locales/zh-tw.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const zhTW: MathDictionary = { + block: { + add_source_text: "新增 LaTeX 方程式", + input_placeholder: "E = mc^2", + preview_error_text: "無效的方程式(點擊編輯)", + }, + inline: { + add_source_text: "新增 LaTeX 方程式", + input_placeholder: "E = mc^2", + preview_error_text: "無效的方程式(點擊編輯)", + }, + slash_menu: { + math_block: { + title: "方程式區塊", + subtext: "獨立的數學方程式區塊", + aliases: ["數學", "公式", "方程式", "latex"], + group: "進階功能", + }, + inline_math: { + title: "行內方程式", + subtext: "文字中的數學符號", + aliases: ["數學", "公式", "方程式", "latex"], + group: "進階功能", + }, + }, + block_type_select: { + name: "方程式", + }, + exporter: { + invalid_formula: (source: string) => `無效的公式 "${source}"`, + }, +}; diff --git a/packages/math-block/src/i18n/locales/zh.ts b/packages/math-block/src/i18n/locales/zh.ts new file mode 100644 index 0000000000..912829eded --- /dev/null +++ b/packages/math-block/src/i18n/locales/zh.ts @@ -0,0 +1,34 @@ +import type { MathDictionary } from "../dictionary.js"; + +export const zh: MathDictionary = { + block: { + add_source_text: "添加 LaTeX 公式", + input_placeholder: "E = mc^2", + preview_error_text: "无效的公式(点击编辑)", + }, + inline: { + add_source_text: "添加 LaTeX 公式", + input_placeholder: "E = mc^2", + preview_error_text: "无效的公式(点击编辑)", + }, + slash_menu: { + math_block: { + title: "块级公式", + subtext: "独立的数学公式块", + aliases: ["数学", "公式", "方程", "latex"], + group: "高级功能", + }, + inline_math: { + title: "行内公式", + subtext: "文本中的数学符号", + aliases: ["数学", "公式", "方程", "latex"], + group: "高级功能", + }, + }, + block_type_select: { + name: "公式", + }, + exporter: { + invalid_formula: (source: string) => `无效的公式 "${source}"`, + }, +}; diff --git a/packages/math-block/src/index.ts b/packages/math-block/src/index.ts new file mode 100644 index 0000000000..cc56a711f3 --- /dev/null +++ b/packages/math-block/src/index.ts @@ -0,0 +1,8 @@ +import * as locales from "./i18n/locales/index.js"; + +export { locales }; +export * from "./i18n/dictionary.js"; +export * from "./block/index.js"; +export * from "./inlineContent/index.js"; +export * from "./helpers/index.js"; +export * from "./getMathSlashMenuItems.js"; diff --git a/packages/math-block/src/inlineContent/createReactMathInlineContentSpec.test.tsx b/packages/math-block/src/inlineContent/createReactMathInlineContentSpec.test.tsx new file mode 100644 index 0000000000..5284d2ca51 --- /dev/null +++ b/packages/math-block/src/inlineContent/createReactMathInlineContentSpec.test.tsx @@ -0,0 +1,261 @@ +import { BlockNoteEditor, BlockNoteSchema } from "@blocknote/core"; +import { BlockNoteViewRaw } from "@blocknote/react"; +import { Node } from "prosemirror-model"; +import { TextSelection } from "prosemirror-state"; +import { flushSync } from "react-dom"; +import { createRoot, Root } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test"; +import { createReactInlineMathSpec } from "./createReactMathInlineContentSpec.js"; + +// TODO: migrate to react, and depreacte jsdom (use vitest browser test?) +/** + * @vitest-environment jsdom + */ + +// Inline math isn't default inline content, so register it in a custom schema. +const schema = BlockNoteSchema.create().extend({ + inlineContentSpecs: { math: createReactInlineMathSpec() }, +}); + +describe.skip("Inline math source popup", () => { + let editor: BlockNoteEditor; + let div: HTMLDivElement; + let root: Root; + + beforeEach(async () => { + // Rendered the same way as `setupTestEditor`: a `BlockNoteViewRaw` into a + // detached div, so the React node view mounts as it does in production. + div = document.createElement("div"); + + editor = BlockNoteEditor.create({ + schema, + trailingBlock: false, + initialContent: [ + { + id: "para", + type: "paragraph", + content: ["before ", { type: "math", content: "a^2" }, " after"], + }, + ], + }); + + root = createRoot(div); + flushSync(() => { + root.render(); + }); + // Let the React node view render before assertions read its DOM. + await flush(); + }); + + afterEach(() => { + root.unmount(); + editor._tiptapEditor.destroy(); + editor = undefined as any; + }); + + /** Yields to the event loop so store-driven React re-renders can flush. */ + function flush() { + return new Promise((resolve) => setTimeout(resolve, 0)); + } + + /** The inline math node and its position in the document. */ + function inlineMath(): { node: Node; pos: number } { + let result: { node: Node; pos: number } | undefined; + editor.prosemirrorState.doc.descendants((node, pos) => { + if (node.type.name === "math") { + result = { node, pos }; + return false; + } + return true; + }); + return result!; + } + + /** The preview-with-source-popup root, which holds `data-open`. */ + function previewRoot(): HTMLElement { + return div.querySelector(".bn-preview-with-source-popup") as HTMLElement; + } + + /** + * Whether the source popup is open. Unlike the block, there's no toggle flag: + * the popup is open exactly when the selection sits inside the inline math. + */ + function isPopupOpen(): boolean { + return previewRoot()?.getAttribute("data-open") === "true"; + } + + /** Places the selection at the given document position. */ + function setSelection(pos: number) { + const view = editor.prosemirrorView!; + view.dispatch( + view.state.tr.setSelection(TextSelection.create(view.state.doc, pos)), + ); + } + + /** Moves the caret to the end of the inline math source (opening the popup). */ + function selectSource() { + const { node, pos } = inlineMath(); + setSelection(pos + node.nodeSize - 1); + } + + /** Dispatches a keydown on the editor DOM, running the ProseMirror keymap. */ + function pressKey(key: string, init: KeyboardEventInit = {}): boolean { + const event = new KeyboardEvent("keydown", { + key, + bubbles: true, + cancelable: true, + ...init, + }); + editor.prosemirrorView!.dom.dispatchEvent(event); + return event.defaultPrevented; + } + + /** The name of the node the selection currently sits in. */ + function selectedNodeType(): string { + return editor.prosemirrorState.selection.$from.node().type.name; + } + + it("renders a MathML preview for the inline math source", () => { + // The source is non-empty, so the preview shows the rendered formula rather + // than the "add source" placeholder button. + expect(previewRoot()).not.toBeNull(); + expect(previewRoot().querySelector("math")).not.toBeNull(); + }); + + it("keeps the popup closed while the cursor is outside the inline math", () => { + editor.setTextCursorPosition("para", "start"); + + expect(isPopupOpen()).toBe(false); + }); + + it("opens the popup when the cursor moves into the source", async () => { + expect(isPopupOpen()).toBe(false); + + selectSource(); + await flush(); + + expect(isPopupOpen()).toBe(true); + // The whole inline content is highlighted while its source is being edited. + expect(previewRoot().classList.contains("ProseMirror-selectednode")).toBe( + true, + ); + }); + + it("closes the popup when the cursor moves back out", async () => { + selectSource(); + await flush(); + expect(isPopupOpen()).toBe(true); + + // Move the caret into the trailing paragraph text, outside the inline math. + editor.setTextCursorPosition("para", "end"); + await flush(); + + expect(isPopupOpen()).toBe(false); + }); + + describe("committing the source", () => { + beforeEach(async () => { + selectSource(); + await flush(); + expect(isPopupOpen()).toBe(true); + }); + + it("Enter moves the selection out of the source and closes the popup", async () => { + pressKey("Enter"); + await flush(); + + expect(isPopupOpen()).toBe(false); + // The caret lands just after the inline math, back in the paragraph. + expect(selectedNodeType()).not.toBe("math"); + }); + + it("Escape moves the selection out of the source and closes the popup", async () => { + pressKey("Escape"); + await flush(); + + expect(isPopupOpen()).toBe(false); + expect(selectedNodeType()).not.toBe("math"); + }); + + it("ArrowUp moves the selection just before the inline content", async () => { + const { pos } = inlineMath(); + + pressKey("ArrowUp"); + await flush(); + + expect(isPopupOpen()).toBe(false); + expect(selectedNodeType()).not.toBe("math"); + // The caret lands just before the inline math (at its start position). + expect(editor.prosemirrorState.selection.from).toBe(pos); + }); + + it("ArrowDown moves the selection just after the inline content", async () => { + const { node, pos } = inlineMath(); + + pressKey("ArrowDown"); + await flush(); + + expect(isPopupOpen()).toBe(false); + expect(selectedNodeType()).not.toBe("math"); + // The caret lands just after the inline math. + expect(editor.prosemirrorState.selection.from).toBe(pos + node.nodeSize); + }); + }); + + describe("clicking the preview", () => { + beforeEach(() => { + editor.setTextCursorPosition("para", "start"); + }); + + it("opens the popup and places the cursor at the source end", async () => { + const container = div.querySelector( + ".bn-preview-container", + ) as HTMLElement; + + // The popup opens on the click; the mousedown is dispatched too for a + // realistic event sequence. + container.dispatchEvent( + new MouseEvent("mousedown", { bubbles: true, cancelable: true }), + ); + container.dispatchEvent( + new MouseEvent("click", { bubbles: true, cancelable: true }), + ); + await flush(); + + expect(isPopupOpen()).toBe(true); + expect(selectedNodeType()).toBe("math"); + // The cursor lands at the end of the source (after "a^2"). + expect(editor.prosemirrorState.selection.$from.parentOffset).toBe(3); + }); + }); + + describe("clicking the OK button", () => { + beforeEach(async () => { + // Open the popup so the OK button has something to close. + selectSource(); + await flush(); + expect(isPopupOpen()).toBe(true); + }); + + it("closes the popup and moves the selection just after the inline content", async () => { + const { node, pos } = inlineMath(); + + const okButton = div.querySelector( + ".bn-code-block-source-popup-ok-button", + ) as HTMLElement; + + okButton.dispatchEvent( + new MouseEvent("mousedown", { bubbles: true, cancelable: true }), + ); + okButton.dispatchEvent( + new MouseEvent("click", { bubbles: true, cancelable: true }), + ); + await flush(); + + expect(isPopupOpen()).toBe(false); + expect(selectedNodeType()).not.toBe("math"); + // The caret lands just after the inline math. + expect(editor.prosemirrorState.selection.from).toBe(pos + node.nodeSize); + }); + }); +}); diff --git a/packages/math-block/src/inlineContent/createReactMathInlineContentSpec.tsx b/packages/math-block/src/inlineContent/createReactMathInlineContentSpec.tsx new file mode 100644 index 0000000000..1ec22d2fe3 --- /dev/null +++ b/packages/math-block/src/inlineContent/createReactMathInlineContentSpec.tsx @@ -0,0 +1,35 @@ +import { CustomInlineContentConfig } from "@blocknote/core"; +import { createReactInlineContentSpec } from "@blocknote/react"; + +import { MathInlineInputRulesExtension } from "./helpers/extensions/MathInlineInputRulesExtension.js"; +import { + parseInlineMathMLContent, + parseInlineMathMLElement, +} from "./helpers/parse/parseInlineMathMLElement.js"; +import { MathInlinePreviewWithPopup } from "./helpers/render/MathInlinePreviewWithPopup.js"; +import { InlineMathMLElement } from "./helpers/toExternalHTML/InlineMathMLElement.js"; + +export const mathInlineContentConfig = { + type: "math" as const, + propSchema: {}, + content: "plain" as const, +} satisfies CustomInlineContentConfig; + +export type MathInlineContentConfig = typeof mathInlineContentConfig; + +export const createReactInlineMathSpec = () => + createReactInlineContentSpec( + mathInlineContentConfig, + { + meta: { + code: true, + highlight: () => "latex", + hasPreview: true, + }, + parse: parseInlineMathMLElement, + parseContent: parseInlineMathMLContent, + render: MathInlinePreviewWithPopup, + toExternalHTML: InlineMathMLElement, + }, + [MathInlineInputRulesExtension], + ); diff --git a/packages/math-block/src/inlineContent/helpers/extensions/MathInlineInputRulesExtension.ts b/packages/math-block/src/inlineContent/helpers/extensions/MathInlineInputRulesExtension.ts new file mode 100644 index 0000000000..c93f200549 --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/extensions/MathInlineInputRulesExtension.ts @@ -0,0 +1,42 @@ +import { createExtension } from "@blocknote/core"; +import { + InputRule, + inputRules as inputRulesPlugin, +} from "@handlewithcare/prosemirror-inputrules"; + +import { mathInlineContentConfig } from "../../createReactMathInlineContentSpec.js"; + +/** + * Converts text wrapped in LaTeX inline-math delimiters into inline math + * content as it's typed: + * - `$...$` (TeX inline math) + * - `\(...\)` (LaTeX inline math) + * + * The delimiters are removed and the enclosed source becomes the inline math's + * content. + */ +export const MathInlineInputRulesExtension = createExtension({ + key: "math-inline-input-rules", + // Cannot use the `inputRules` field as it only allows for converting matched + // content to blocks. + prosemirrorPlugins: [ + inputRulesPlugin({ + rules: [/\$([^$]+)\$$/, /\\\((.+?)\\\)$/].map( + (find) => + new InputRule(find, (state, match, start, end) => { + const source = match[1]?.trim(); + const nodeType = state.schema.nodes[mathInlineContentConfig.type]; + if (!source || !nodeType) { + return null; + } + + return state.tr.replaceRangeWith( + start, + end, + nodeType.create(null, state.schema.text(source)), + ); + }), + ), + }), + ], +}); diff --git a/packages/math-block/src/inlineContent/helpers/extensions/index.ts b/packages/math-block/src/inlineContent/helpers/extensions/index.ts new file mode 100644 index 0000000000..cc91f3d80f --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/extensions/index.ts @@ -0,0 +1 @@ +export * from "./MathInlineInputRulesExtension.js"; diff --git a/packages/math-block/src/inlineContent/helpers/index.ts b/packages/math-block/src/inlineContent/helpers/index.ts new file mode 100644 index 0000000000..03c47b6db5 --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/index.ts @@ -0,0 +1,4 @@ +export * from "./extensions/index.js"; +export * from "./parse/index.js"; +export * from "./render/index.js"; +export * from "./toExternalHTML/index.js"; diff --git a/packages/math-block/src/inlineContent/helpers/parse/index.ts b/packages/math-block/src/inlineContent/helpers/parse/index.ts new file mode 100644 index 0000000000..0dc4eea7c7 --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/parse/index.ts @@ -0,0 +1 @@ +export * from "./parseInlineMathMLElement.js"; diff --git a/packages/math-block/src/inlineContent/helpers/parse/parseInlineMathMLElement.ts b/packages/math-block/src/inlineContent/helpers/parse/parseInlineMathMLElement.ts new file mode 100644 index 0000000000..bcfb874363 --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/parse/parseInlineMathMLElement.ts @@ -0,0 +1,28 @@ +import { Fragment, type Schema } from "prosemirror-model"; + +export const parseInlineMathMLElement = (el: HTMLElement) => + el.nodeName.toLowerCase() === "math" && + el.getAttribute("display") === "inline" + ? {} + : undefined; + +export const parseInlineMathMLContent = ({ + el, + schema, +}: { + el: HTMLElement; + schema: Schema; +}) => { + const annotations = Array.from(el.getElementsByTagName("annotation")); + const texAnnotation = annotations.find( + (annotation) => annotation.getAttribute("encoding") === "application/x-tex", + ); + + const latex = texAnnotation?.textContent?.trim(); + + if (!latex) { + return undefined; + } + + return Fragment.from(schema.text(latex)); +}; diff --git a/packages/math-block/src/inlineContent/helpers/render/MathInlinePreviewWithPopup.tsx b/packages/math-block/src/inlineContent/helpers/render/MathInlinePreviewWithPopup.tsx new file mode 100644 index 0000000000..c036f5e8b6 --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/render/MathInlinePreviewWithPopup.tsx @@ -0,0 +1,54 @@ +import { StyleSchema } from "@blocknote/core"; +import { + PreviewPlaceholder, + ReactCustomInlineContentRenderProps, + SourceInlineContentWithPreview, +} from "@blocknote/react"; +import { TbMathFunction } from "react-icons/tb"; + +import { useLatexToMathMLString } from "../../../helpers/render/useLatexToMathML.js"; +import { getMathDictionary } from "../../../i18n/dictionary.js"; +import { MathInlineContentConfig } from "../../createReactMathInlineContentSpec.js"; + +export const MathInlinePreviewWithPopup = ( + props: ReactCustomInlineContentRenderProps< + MathInlineContentConfig, + StyleSchema + >, +) => { + const source = props.inlineContent.content.trim(); + const { mathMLString, error } = useLatexToMathMLString(source, true); + const dict = getMathDictionary(props.editor).inline; + + return ( + + ) : undefined + } + error={error} + errorPreview={ + } + text={dict.preview_error_text} + /> + } + emptySourcePlaceholder={ + } + text={dict.add_source_text} + /> + } + sourcePlaceholder={dict.input_placeholder} + /> + ); +}; diff --git a/packages/math-block/src/inlineContent/helpers/render/index.ts b/packages/math-block/src/inlineContent/helpers/render/index.ts new file mode 100644 index 0000000000..36d99003c7 --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/render/index.ts @@ -0,0 +1 @@ +export * from "./MathInlinePreviewWithPopup.js"; diff --git a/packages/math-block/src/inlineContent/helpers/toExternalHTML/InlineMathMLElement.tsx b/packages/math-block/src/inlineContent/helpers/toExternalHTML/InlineMathMLElement.tsx new file mode 100644 index 0000000000..a313d8a1ae --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/toExternalHTML/InlineMathMLElement.tsx @@ -0,0 +1,36 @@ +import { StyleSchema } from "@blocknote/core"; +import { ReactCustomInlineContentRenderProps } from "@blocknote/react"; +import type { ComponentType } from "react"; + +import { latexToMathMLElement } from "../../../helpers/toExternalHTML/latexToMathMLElement.js"; +import { MathInlineContentConfig } from "../../createReactMathInlineContentSpec.js"; + +export const InlineMathMLElement = ({ + inlineContent, +}: ReactCustomInlineContentRenderProps< + MathInlineContentConfig, + StyleSchema +>) => { + const { mathMLElement } = latexToMathMLElement(inlineContent.content, true); + if (!mathMLElement) { + return null; + } + + // `math` isn't part of React's built-in JSX types, so we alias it to a + // component type to render it as a JSX element. + const Math = "math" as unknown as ComponentType<{ + xmlns: string; + display: string; + alttext: string; + dangerouslySetInnerHTML: { __html: string }; + }>; + + return ( + + ); +}; diff --git a/packages/math-block/src/inlineContent/helpers/toExternalHTML/index.ts b/packages/math-block/src/inlineContent/helpers/toExternalHTML/index.ts new file mode 100644 index 0000000000..91edfdaa0d --- /dev/null +++ b/packages/math-block/src/inlineContent/helpers/toExternalHTML/index.ts @@ -0,0 +1 @@ +export * from "./InlineMathMLElement.js"; diff --git a/packages/math-block/src/inlineContent/index.ts b/packages/math-block/src/inlineContent/index.ts new file mode 100644 index 0000000000..a3c09ffd17 --- /dev/null +++ b/packages/math-block/src/inlineContent/index.ts @@ -0,0 +1,2 @@ +export * from "./createReactMathInlineContentSpec.js"; +export * from "./helpers/index.js"; diff --git a/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/content.xml b/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/content.xml new file mode 100644 index 0000000000..aefe0cdde9 --- /dev/null +++ b/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/content.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Inline math: + + + + + + + \ No newline at end of file diff --git a/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/objects.xml b/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/objects.xml new file mode 100644 index 0000000000..db9967e38b --- /dev/null +++ b/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/objects.xml @@ -0,0 +1,81 @@ + + + + + + + + a + + + 2 + + + + = + + + + + + b + + + 2 + + + + + + + + + c + + + 2 + + + + + + + a^2 = \sqrt{b^2 + c^2} + + + + + + + + + + + e + + + + i + + + π + + + + + + + + + 1 + + + = + + + 0 + + + + e^{i\pi} + 1 = 0 + + + \ No newline at end of file diff --git a/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/styles.xml b/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/styles.xml new file mode 100644 index 0000000000..9a53dac929 --- /dev/null +++ b/packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/styles.xml @@ -0,0 +1,599 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/math-block/src/odt-exporter/index.ts b/packages/math-block/src/odt-exporter/index.ts new file mode 100644 index 0000000000..07ada74686 --- /dev/null +++ b/packages/math-block/src/odt-exporter/index.ts @@ -0,0 +1,180 @@ +import type { + BlockConfig, + BlockFromConfigNoChildren, + Exporter, +} from "@blocknote/core"; +import { plainContentToString } from "@blocknote/core"; +import { ODTExporter } from "@blocknote/xl-odt-exporter"; +import { createElement } from "react"; + +import { latexToMathML } from "../exporterHelpers/latexToMathML.js"; +import { getMathExporterDictionary } from "../i18n/dictionary.js"; + +// The ODT elements are created with `createElement` string tags rather than +// JSX: the ODT exporter's namespaced tags (`text:p`, `draw:frame`, ...) need +// JSX runtime module augmentation plus a transform that allows namespaces, +// neither of which this package sets up for its React sources. + +type MathBlock = BlockFromConfigNoChildren< + BlockConfig<"mathBlock", {}, "plain">, + any, + any +>; + +type InlineMath = { type: "math"; content: string }; + +// A formula object, anchored as a character so it can sit inline among text. +// The MathML goes into an object sub-document (rather than inline into the +// frame) and the frame gets no explicit size, with a graphic style derived +// from the built-in "Formula" style - this exact combination makes +// LibreOffice load the formula as a real formula object and compute its +// natural size (sized frames get the formula scaled-to-fit instead, and +// inline MathML renders at zero size). +function formulaFrame(exporter: ODTExporter, mathML: string) { + const objectPath = exporter.registerObject( + '\n' + mathML, + ); + const styleName = exporter.registerStyle((name) => + createElement( + "style:style", + { + "style:family": "graphic", + "style:name": name, + "style:parent-style-name": "Formula", + }, + createElement("style:graphic-properties", { + "style:vertical-pos": "middle", + "style:vertical-rel": "text", + }), + ), + ); + + return createElement( + "draw:frame", + { "draw:style-name": styleName, "text:anchor-type": "as-char" }, + createElement("draw:object", { + "xlink:href": objectPath, + "xlink:type": "simple", + "xlink:show": "embed", + "xlink:actuate": "onLoad", + }), + ); +} + +// Mirrors the editor, which shows the error state in the preview +// placeholder, identifying the formula by its source. The parser's message +// is deliberately NOT rendered: it's authoring detail (and untranslated +// English) - the editor is where the author sees and fixes it. Styled muted like the other +// exporters' placeholders. +function errorText(source: string, exporter: ODTExporter) { + const styleName = exporter.registerStyle((name) => + createElement( + "style:style", + { "style:family": "text", "style:name": name }, + createElement("style:text-properties", { + "fo:font-style": "italic", + "fo:color": "#999999", + }), + ), + ); + + return createElement( + "text:span", + { "text:style-name": styleName }, + getMathExporterDictionary(exporter).invalid_formula(source), + ); +} + +/** + * ODT block mapping for `@blocknote/math-block` that renders math blocks as + * native (editable) formula objects. Invalid LaTeX renders an error + * placeholder (mirroring the editor): + * + * ```ts + * import { mathBlockMapping } from "@blocknote/math-block/odt-exporter"; + * + * new ODTExporter(schema, { + * ...odtDefaultSchemaMappings, + * blockMapping: { + * ...odtDefaultSchemaMappings.blockMapping, + * mathBlock: mathBlockMapping, + * }, + * }); + * ``` + */ +export function mathBlockMapping( + block: MathBlock, + exporter: Exporter, +) { + // Only the ODTExporter invokes ODT mappings, but mapping signatures are + // contravariant in the exporter parameter, so requiring the subclass here + // wouldn't satisfy the mapping type - hence the base type + cast. + const odtExporter = exporter as ODTExporter; + const source = plainContentToString(block.content); + if (!source.trim()) { + return createElement("text:p"); + } + + const mathML = latexToMathML(source, false); + if (mathML.error !== undefined) { + return createElement("text:p", null, errorText(source, odtExporter)); + } + + const styleName = odtExporter.registerStyle((name) => + createElement( + "style:style", + { + "style:family": "paragraph", + "style:name": name, + "style:parent-style-name": "Standard", + }, + createElement("style:paragraph-properties", { + "fo:text-align": "center", + }), + ), + ); + + return createElement( + "text:p", + { "text:style-name": styleName }, + formulaFrame(odtExporter, mathML.mathML), + ); +} + +/** + * ODT inline content mapping for `@blocknote/math-block` that renders inline + * math as native (editable) formula objects. Invalid LaTeX renders an error + * placeholder (mirroring the editor): + * + * ```ts + * import { inlineMathMapping } from "@blocknote/math-block/odt-exporter"; + * + * new ODTExporter(schema, { + * ...odtDefaultSchemaMappings, + * inlineContentMapping: { + * ...odtDefaultSchemaMappings.inlineContentMapping, + * math: inlineMathMapping, + * }, + * }); + * ``` + */ +export function inlineMathMapping( + inlineContent: InlineMath, + exporter: Exporter, +) { + // Only the ODTExporter invokes ODT mappings, but mapping signatures are + // contravariant in the exporter parameter, so requiring the subclass here + // wouldn't satisfy the mapping type - hence the base type + cast. + const odtExporter = exporter as ODTExporter; + const source = inlineContent.content; + if (!source.trim()) { + return createElement("text:span"); + } + + const mathML = latexToMathML(source, true); + if (mathML.error !== undefined) { + return errorText(source, odtExporter); + } + + return formulaFrame(odtExporter, mathML.mathML); +} diff --git a/packages/math-block/src/odt-exporter/odtExporter.test.ts b/packages/math-block/src/odt-exporter/odtExporter.test.ts new file mode 100644 index 0000000000..8fa245c29e --- /dev/null +++ b/packages/math-block/src/odt-exporter/odtExporter.test.ts @@ -0,0 +1,124 @@ +import { + BlockNoteSchema, + createPageBreakBlockSpec, + defaultBlockSpecs, +} from "@blocknote/core"; +import { + ODTExporter, + odtDefaultSchemaMappings, +} from "@blocknote/xl-odt-exporter"; +import { testODTDocumentAgainstSnapshot } from "@shared/util/odtTestUtil.js"; +import { testDocumentWithSourceBlocks } from "@shared/testDocument.js"; +import { testResolveFileUrl } from "@shared/util/testFileResolver.js"; +import { BlobReader, FileEntry, TextWriter, ZipReader } from "@zip.js/zip.js"; +import { beforeAll, describe, expect, it } from "vite-plus/test"; + +import { inlineMathMapping, mathBlockMapping } from "./index.js"; + +beforeAll(async () => { + // @ts-expect-error - Blob polyfill for Node test environment + globalThis.Blob = (await import("node:buffer")).Blob; +}); + +describe("odt exporter mappings", () => { + it("should render error placeholders for invalid LaTeX", async () => { + // Assembled outside the constructor call as the schema doesn't include + // the math specs - like the default mappings, the math entries just map + // the block JSON. + const mappings = { + ...odtDefaultSchemaMappings, + blockMapping: { + ...odtDefaultSchemaMappings.blockMapping, + mathBlock: mathBlockMapping, + }, + inlineContentMapping: { + ...odtDefaultSchemaMappings.inlineContentMapping, + math: inlineMathMapping, + }, + }; + const exporter = new ODTExporter( + BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + pageBreak: createPageBreakBlockSpec(), + }, + }), + mappings, + { resolveFileUrl: testResolveFileUrl }, + ); + + const odt = await exporter.toODTDocument([ + { + id: "1", + type: "mathBlock", + props: {}, + content: [{ type: "text", text: "\\invalidcommand{", styles: {} }], + children: [], + }, + { + id: "2", + type: "paragraph", + props: {}, + content: [ + { type: "text", text: "Broken: ", styles: {} }, + { type: "math", props: {}, content: "\\invalidcommand{" }, + ], + children: [], + }, + ] as any); + const zipReader = new ZipReader(new BlobReader(odt)); + const entries = await zipReader.getEntries(); + const contentXML = entries.find( + (entry) => entry.filename === "content.xml", + ) as FileEntry; + const content = await contentXML.getData(new TextWriter()); + + // Mirrors the editor's error placeholder rather than dumping the LaTeX + // source on readers - once for the block, once for the inline math. + expect(content.match(/Invalid formula/g)).toHaveLength(2); + expect(content).not.toContain("draw:object"); + }); + + it("should export math as native formulas", { timeout: 10000 }, async () => { + // Assembled outside the constructor call as the schema doesn't include + // the math specs - like the default mappings, the math entries just + // map the block JSON. + const mappings = { + ...odtDefaultSchemaMappings, + blockMapping: { + ...odtDefaultSchemaMappings.blockMapping, + mathBlock: mathBlockMapping, + }, + inlineContentMapping: { + ...odtDefaultSchemaMappings.inlineContentMapping, + math: inlineMathMapping, + }, + }; + const exporter = new ODTExporter( + BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + pageBreak: createPageBreakBlockSpec(), + }, + }), + mappings, + { resolveFileUrl: testResolveFileUrl }, + ); + + // The math block & inline math paragraph from the shared test document. + const odt = await exporter.toODTDocument( + testDocumentWithSourceBlocks.filter((block) => + ["math-block", "paragraph-with-inline-math"].includes(block.id), + ), + ); + // The math block & the inline math each embed one formula object. + await testODTDocumentAgainstSnapshot(odt, { + styles: "__snapshots__/withMathMappings/styles.xml", + content: "__snapshots__/withMathMappings/content.xml", + objects: { + snapshot: "__snapshots__/withMathMappings/objects.xml", + expectedCount: 2, + }, + }); + }); +}); diff --git a/packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsx b/packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsx new file mode 100644 index 0000000000..cd3a3b0a49 --- /dev/null +++ b/packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsx @@ -0,0 +1,60 @@ + + + + + + + {`a^2 = \sqrt{b^2 + c^2}`} + + + + + + + + + Inline math:{' '} + + + + + + + \ No newline at end of file diff --git a/packages/math-block/src/pdf-exporter/index.tsx b/packages/math-block/src/pdf-exporter/index.tsx new file mode 100644 index 0000000000..32b5ca5087 --- /dev/null +++ b/packages/math-block/src/pdf-exporter/index.tsx @@ -0,0 +1,174 @@ +import type { + BlockConfig, + BlockFromConfigNoChildren, + Exporter, +} from "@blocknote/core"; +import { exportImageToDataURL, plainContentToString } from "@blocknote/core"; +import { Math } from "@react-pdf/math"; +import { Image, Text, View } from "@react-pdf/renderer"; + +import { + latexToMathSVG, + RasterizeSVG, + rasterizeSVGInBrowser, +} from "../exporterHelpers/renderMathToImage.js"; +import { getMathExporterDictionary } from "../i18n/dictionary.js"; + +type MathBlock = BlockFromConfigNoChildren< + BlockConfig<"mathBlock", {}, "plain">, + any, + any +>; + +type InlineMath = { type: "math"; content: string }; + +export { + latexToMathSVG, + rasterizeSVGInBrowser, +} from "../exporterHelpers/renderMathToImage.js"; +export type { + RasterizeSVG, + SVGExportImage, +} from "../exporterHelpers/renderMathToImage.js"; + +// The PDF exporter's body text is 12pt (16px at 0.75 pixels per point). +const FONT_SIZE_POINTS = 16 * 0.75; + +// Mirrors the editor, which shows the error state in the preview +// placeholder, identifying the formula by its source. The parser's message +// is deliberately NOT rendered: it's authoring detail (and untranslated +// English) - the editor is where the author sees and fixes it. +function errorText( + exporter: Exporter, + source: string, + key: string, +) { + return ( + + {getMathExporterDictionary(exporter).invalid_formula(source)} + + ); +} + +/** + * PDF block mapping for `@blocknote/math-block` that renders math blocks as + * actual formulas (via `@react-pdf/math`, which converts the LaTeX to SVG + * paths with MathJax). Invalid LaTeX renders an error placeholder + * (mirroring the editor): + * + * ```ts + * import { mathBlockMapping } from "@blocknote/math-block/pdf-exporter"; + * + * new PDFExporter(schema, { + * ...pdfDefaultSchemaMappings, + * blockMapping: { + * ...pdfDefaultSchemaMappings.blockMapping, + * mathBlock: mathBlockMapping, + * }, + * }); + * ``` + */ +export function mathBlockMapping( + block: MathBlock, + exporter: Exporter, +) { + const source = plainContentToString(block.content); + if (!source.trim()) { + return ; + } + + // `Math` renders MathJax's own error output for invalid LaTeX; validate + // up front to render the editor-style error placeholder instead. + const validation = latexToMathSVG(source, { + inline: false, + fontSize: FONT_SIZE_POINTS, + }); + if (validation.error !== undefined) { + return ( + + {errorText(exporter, source, "math-error")} + + ); + } + + return ( + + {source} + + ); +} + +/** + * Creates a PDF inline content mapping for `@blocknote/math-block` that + * renders inline math as formulas, rasterized to images that flow inline + * with the text (react-pdf drops SVG elements inside `Text`, so the vector + * output used for math blocks isn't an option here). Note that the image + * sits on the text baseline, so expressions with depth (fractions, + * subscripts) render slightly raised - and it's sized for the exporter's + * 12pt body text (mappings get no font context from react-pdf), so inline + * math inside headings renders at body-text size. + * + * Rasterization runs in the browser by default; when exporting elsewhere + * (e.g. server-side), pass a `rasterize` function backed by an SVG + * rasterizer such as `@resvg/resvg-js` or `sharp`. Invalid LaTeX renders an + * error placeholder (mirroring the editor): + * + * ```ts + * import { createInlineMathMapping } from "@blocknote/math-block/pdf-exporter"; + * + * new PDFExporter(schema, { + * ...pdfDefaultSchemaMappings, + * inlineContentMapping: { + * ...pdfDefaultSchemaMappings.inlineContentMapping, + * math: createInlineMathMapping({ rasterize }), + * }, + * }); + * ``` + */ +export function createInlineMathMapping(options?: { + rasterize?: RasterizeSVG; +}) { + return ( + inlineContent: InlineMath, + exporter: Exporter, + ) => { + const source = inlineContent.content; + if (!source.trim()) { + return ; + } + + const rasterize = options?.rasterize ?? rasterizeSVGInBrowser; + if (!options?.rasterize && typeof document === "undefined") { + throw new Error( + "Rendering inline math requires rasterizing SVGs, which the built-in rasterizer can only do in the browser. When exporting elsewhere, pass a `rasterize` function to `createInlineMathMapping` (e.g. backed by @resvg/resvg-js or sharp).", + ); + } + + // The metrics are needed synchronously for react-pdf's text layout; only + // the rasterization itself is deferred, via react-pdf's support for + // async `src` functions (resolved before layout). Rasterization failures + // are unexpected and propagate - react-pdf skips the image and warns. + const result = latexToMathSVG(source, { + inline: true, + fontSize: FONT_SIZE_POINTS, + }); + if (result.error !== undefined) { + return errorText(exporter, source, "inlineMath"); + } + + return ( + rasterize(result.image).then(exportImageToDataURL)} + style={{ width: result.image.width, height: result.image.height }} + /> + ); + }; +} + +/** + * PDF inline content mapping for `@blocknote/math-block` with the default + * options - see {@link createInlineMathMapping}. Browser-only; when + * exporting elsewhere, use the factory to pass a `rasterize` function. + */ +export const inlineMathMapping = createInlineMathMapping(); diff --git a/packages/math-block/src/pdf-exporter/pdfExporter.test.tsx b/packages/math-block/src/pdf-exporter/pdfExporter.test.tsx new file mode 100644 index 0000000000..46dde732f7 --- /dev/null +++ b/packages/math-block/src/pdf-exporter/pdfExporter.test.tsx @@ -0,0 +1,140 @@ +import { + BlockNoteSchema, + createPageBreakBlockSpec, + defaultBlockSpecs, + ExportImage, +} from "@blocknote/core"; +import { + PDFExporter, + pdfDefaultSchemaMappings, +} from "@blocknote/xl-pdf-exporter"; +import { testDocumentWithSourceBlocks } from "@shared/testDocument.js"; +import reactElementToJSXString from "react-element-to-jsx-string"; +import { describe, expect, it } from "vite-plus/test"; + +import { + createInlineMathMapping, + inlineMathMapping, + mathBlockMapping, +} from "./index.js"; + +// A stub rasterizer, standing in for e.g. @resvg/resvg-js on a server - the +// real (browser-only) rasterization is covered by the browser test suite. +const rasterize = async (svg: ExportImage) => ({ + mimeType: "image/png", + data: new Uint8Array([0, 0, 0]), + width: svg.width, + height: svg.height, +}); + +function createExporter( + inlineMath: ReturnType, +) { + // Assembled outside the constructor call as the schema doesn't include + // the math specs - like the default mappings, the math entries just map + // the block JSON. + const mappings = { + ...pdfDefaultSchemaMappings, + blockMapping: { + ...pdfDefaultSchemaMappings.blockMapping, + mathBlock: mathBlockMapping, + }, + inlineContentMapping: { + ...pdfDefaultSchemaMappings.inlineContentMapping, + math: inlineMath, + }, + }; + + return new PDFExporter( + BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + pageBreak: createPageBreakBlockSpec(), + }, + }), + mappings, + ); +} + +describe("pdf exporter mappings", () => { + it("should export math as formulas and inline math as images", async () => { + const exporter = createExporter(createInlineMathMapping({ rasterize })); + + // The math block & inline math paragraph from the shared test document. + const transformed = await exporter.toReactPDFDocument( + testDocumentWithSourceBlocks.filter((block) => + ["math-block", "paragraph-with-inline-math"].includes(block.id), + ), + ); + const str = reactElementToJSXString(transformed); + + await expect(str).toMatchFileSnapshot( + "__snapshots__/exampleWithMathMappings.jsx", + ); + }); + + it("should render error placeholders for invalid LaTeX", async () => { + const exporter = createExporter(createInlineMathMapping({ rasterize })); + + const transformed = await exporter.toReactPDFDocument([ + { + id: "1", + type: "mathBlock", + props: {}, + content: [{ type: "text", text: "\\invalidcommand{", styles: {} }], + children: [], + }, + { + id: "2", + type: "paragraph", + props: {}, + content: [ + { type: "text", text: "Broken: ", styles: {} }, + { type: "math", props: {}, content: "\\invalidcommand{" }, + ], + children: [], + }, + ] as any); + const str = reactElementToJSXString(transformed); + + // Mirrors the editor's error placeholder rather than dumping the LaTeX + // source on readers - once for the block, once for the inline math. + expect(str.match(/Invalid formula/g)).toHaveLength(2); + }); + + it("should render empty math as nothing", async () => { + // Empty source isn't an error - there's just nothing to render (and no + // rasterizer is needed). + const exporter = createExporter(inlineMathMapping); + + const transformed = await exporter.toReactPDFDocument([ + { id: "1", type: "mathBlock", props: {}, content: [], children: [] }, + { + id: "2", + type: "paragraph", + props: {}, + content: [{ type: "math", props: {}, content: "" }], + children: [], + }, + ] as any); + const str = reactElementToJSXString(transformed); + + expect(str).not.toContain("Invalid formula"); + expect(str).not.toContain("Math"); + }); + + it("should throw a descriptive error without a rasterizer outside the browser", async () => { + // The default mapping's built-in rasterizer only works in the browser, + // and silently degrading is worse than failing loudly - the error names + // the `rasterize` option to pass. + const exporter = createExporter(inlineMathMapping); + + await expect( + exporter.toReactPDFDocument( + testDocumentWithSourceBlocks.filter( + (block) => block.id === "paragraph-with-inline-math", + ), + ), + ).rejects.toThrow("pass a `rasterize` function"); + }); +}); diff --git a/packages/math-block/src/vite-env.d.ts b/packages/math-block/src/vite-env.d.ts new file mode 100644 index 0000000000..bc2d8a36f3 --- /dev/null +++ b/packages/math-block/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/math-block/tsconfig.json b/packages/math-block/tsconfig.json new file mode 100644 index 0000000000..2d8bcd4a25 --- /dev/null +++ b/packages/math-block/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noEmit": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "outDir": "dist", + "declaration": true, + "declarationDir": "types", + "composite": true, + "skipLibCheck": true, + "emitDeclarationOnly": true, + "paths": { + "@shared/*": ["../../shared/*"] + } + }, + "include": ["src"], + "references": [ + { + "path": "../../shared" + } + ] +} diff --git a/packages/math-block/vite.config.ts b/packages/math-block/vite.config.ts new file mode 100644 index 0000000000..e9d8aafbfa --- /dev/null +++ b/packages/math-block/vite.config.ts @@ -0,0 +1,135 @@ +import * as path from "path"; +import { webpackStats } from "rollup-plugin-webpack-stats"; +import { configDefaults, defineConfig, type UserConfig } from "vite-plus"; +import pkg from "./package.json"; + +// https://vitejs.dev/config/ +export default defineConfig( + (conf) => + ({ + run: { + tasks: { + build: { + command: "tsc && vp build", + input: [ + { auto: true }, + { pattern: "!**/*.tsbuildinfo", base: "workspace" }, + ], + output: ["dist/**", "!dist/*.tsbuildinfo"], + }, + }, + }, + test: { + setupFiles: ["./vitestSetup.ts"], + // `.browser.test` files need a real browser; the tests package's + // browser suite runs them. + exclude: [...configDefaults.exclude, "**/*.browser.test.*"], + }, + // The ODT exporter sources (loaded via the test aliases) use JSX + // namespace tags (e.g. ), which Vite's oxc rejects by default. + oxc: { + jsx: { + throwIfNamespace: false, + }, + }, + plugins: [webpackStats() as any], + // used so that vitest resolves the core package from the sources instead of the built version + resolve: { + alias: + conf.command === "build" + ? ({} as Record) + : ({ + "@shared": path.resolve(__dirname, "../../shared/"), + // load live from sources with live reload working + "@blocknote/core": path.resolve(__dirname, "../core/src/"), + "@blocknote/react": path.resolve(__dirname, "../react/src/"), + "@blocknote/xl-docx-exporter": path.resolve( + __dirname, + "../xl-docx-exporter/src/", + ), + "@blocknote/xl-email-exporter": path.resolve( + __dirname, + "../xl-email-exporter/src/", + ), + "@blocknote/xl-multi-column": path.resolve( + __dirname, + "../xl-multi-column/src/", + ), + "@blocknote/xl-odt-exporter": path.resolve( + __dirname, + "../xl-odt-exporter/src/", + ), + "@blocknote/xl-pdf-exporter": path.resolve( + __dirname, + "../xl-pdf-exporter/src/", + ), + } as Record), + }, + build: { + sourcemap: true, + lib: { + entry: { + "blocknote-math-block": path.resolve(__dirname, "src/index.ts"), + "docx-exporter": path.resolve( + __dirname, + "src/docx-exporter/index.ts", + ), + "odt-exporter": path.resolve( + __dirname, + "src/odt-exporter/index.ts", + ), + "pdf-exporter": path.resolve( + __dirname, + "src/pdf-exporter/index.tsx", + ), + "email-exporter": path.resolve( + __dirname, + "src/email-exporter/index.tsx", + ), + }, + name: "blocknote-math-block", + formats: ["es", "cjs"], + fileName: (format, entryName) => + format === "es" ? `${entryName}.js` : `${entryName}.cjs`, + }, + rollupOptions: { + // make sure to externalize deps that shouldn't be bundled + // into your library + external: (source) => { + // Bundle react-icons into the output (tree-shaken) so consumers + // don't need to install it as a peer/runtime dependency. + const bundledDeps = ["react-icons"]; + if ( + bundledDeps.some( + (dep) => source === dep || source.startsWith(dep + "/"), + ) + ) { + return false; + } + if ( + Object.keys({ + ...pkg.dependencies, + ...((pkg as any).peerDependencies || {}), + ...pkg.devDependencies, + }).some((dep) => source === dep || source.startsWith(dep + "/")) + ) { + return true; + } + return ( + source.startsWith("react/") || + source.startsWith("react-dom/") || + source.startsWith("prosemirror-") || + source.startsWith("@tiptap/") || + source.startsWith("@blocknote/") || + source.startsWith("node:") + ); + }, + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: {}, + }, + }, + }, + }) as UserConfig, +); diff --git a/packages/math-block/vitestSetup.ts b/packages/math-block/vitestSetup.ts new file mode 100644 index 0000000000..dbcf3eb39c --- /dev/null +++ b/packages/math-block/vitestSetup.ts @@ -0,0 +1,10 @@ +import { afterEach, beforeEach } from "vite-plus/test"; + +beforeEach(() => { + globalThis.window = globalThis.window || ({} as any); + (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {}; +}); + +afterEach(() => { + delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; +}); diff --git a/packages/react/package.json b/packages/react/package.json index 7e29004226..5d81c5bf05 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -78,7 +78,7 @@ "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plugin-externalize-deps": "^0.10.0", "vite-plus": "catalog:" }, diff --git a/packages/react/src/blocks/SourceWithPreview/PreviewPlaceholder.tsx b/packages/react/src/blocks/SourceWithPreview/PreviewPlaceholder.tsx new file mode 100644 index 0000000000..7a2c8b1625 --- /dev/null +++ b/packages/react/src/blocks/SourceWithPreview/PreviewPlaceholder.tsx @@ -0,0 +1,31 @@ +import { ReactNode } from "react"; +import { FaCode } from "react-icons/fa"; +import { MdErrorOutline } from "react-icons/md"; + +/** + * Shown in place of the preview when there's nothing to render: the "add + * source" state when the source is empty, or - with `error` - the compact + * error state when the source failed to render (the full error message is + * shown in the source popup while editing). + */ +export const PreviewPlaceholder = (props: { + text: string; + icon?: ReactNode; + error?: boolean; +}) => ( +
+ {/* The icon is decorative next to the text, so hide it from screen + readers - react-icons don't set this themselves (some even carry + `role="img"`, announcing a nameless image). */} + +

{props.text}

+
+); diff --git a/packages/react/src/blocks/SourceWithPreview/SourcePreviewPopup.ts b/packages/react/src/blocks/SourceWithPreview/SourcePreviewPopup.ts new file mode 100644 index 0000000000..7f78539376 --- /dev/null +++ b/packages/react/src/blocks/SourceWithPreview/SourcePreviewPopup.ts @@ -0,0 +1,25 @@ +/** + * State of & actions on the source popup of a block or inline content with a + * preview. Returned by `useSourceBlockPreviewPopup` and + * `useSourceInlineContentPreviewPopup`. + */ +export type SourcePreviewPopup = { + /** + * Whether the source popup is open. + */ + isOpen: boolean; + /** + * Whether the block/inline content is selected, i.e. whether its preview is + * highlighted. + */ + isSelected: boolean; + /** + * Opens the popup, moves the cursor into the source, and focuses the + * editor. Does nothing when the editor isn't editable. + */ + open: () => void; + /** + * Closes the popup. + */ + close: () => void; +}; diff --git a/packages/react/src/blocks/SourceWithPreview/SourceWithPreview.tsx b/packages/react/src/blocks/SourceWithPreview/SourceWithPreview.tsx new file mode 100644 index 0000000000..65be1cfa13 --- /dev/null +++ b/packages/react/src/blocks/SourceWithPreview/SourceWithPreview.tsx @@ -0,0 +1,252 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { MouseEvent, ReactNode, useId, useRef } from "react"; +import { MdKeyboardReturn } from "react-icons/md"; + +import { PreviewPlaceholder } from "./PreviewPlaceholder.js"; +import { SourcePreviewPopup } from "./SourcePreviewPopup.js"; + +/** + * Props shared by {@link SourceBlockWithPreview} and + * {@link SourceInlineContentWithPreview}. + */ +export type SourceWithPreviewProps = { + /** + * Ref for the element holding the editable source content. + */ + contentRef: (node: HTMLElement | null) => void; + /** + * The source as plain text. When empty, an "add source" button is shown in + * place of the preview. + */ + source: string; + /** + * The rendered preview (e.g. a formula or diagram). When the current source + * has an error, pass the last successfully rendered preview so it stays up + * while the user edits, or `undefined` when there is none - the error state + * is shown in its place. The last-good preview only stays up within the + * editing session that introduced the error: once the popup closes on an + * erroneous source, the error state shows until the source renders + * successfully again. + */ + preview?: ReactNode; + /** + * Error from rendering the preview, shown below the source in the popup. + * Accepts arbitrary elements, so actions (e.g. a button that fixes the + * source) can be rendered alongside the error message. + */ + error?: ReactNode; + /** + * Shown in place of the preview when the source is empty. A string sets + * the text of the default "add source" button, while an element replaces + * the button entirely. Defaults to the editor dictionary's + * `code_block.add_source_button_text`. + */ + emptySourcePlaceholder?: ReactNode; + /** + * Shown in place of the preview when the source has an error, unless + * there's a last-good `preview` and the error hasn't been committed yet + * (the popup hasn't closed since it appeared). A string sets the text of + * the default compact error state, while an element replaces it entirely. + * The full `error` is only shown in the popup while editing. + */ + errorPreview?: ReactNode; + /** + * Placeholder shown in the source input popup while the source is empty + * (e.g. "Enter a LaTeX equation"). + */ + sourcePlaceholder?: string; +}; + +/** + * Renders a preview of source content (e.g. math or diagrams), with the + * editable source in a popup driven by the given popup controller: decides + * what shows in place of the preview (empty state, last-good preview, or + * error state) and opens/closes the popup on clicks. + * `SourceBlockWithPreview` and `SourceInlineContentWithPreview` are thin + * per-kind wrappers around this, obtaining the popup controller from their + * respective hooks. + */ +export const SourceWithPreview = ( + props: SourceWithPreviewProps & { + editor: BlockNoteEditor; + popup: SourcePreviewPopup; + /** + * Renders with `span` wrappers for inline content. + */ + inline?: boolean; + /** + * Whether pressing Enter in the source popup does what the "OK" button + * does (closing the popup). Shows the return-key icon on the "OK" button. + */ + enterSubmits?: boolean; + }, +) => { + const { + editor, + popup, + inline, + enterSubmits = true, + contentRef, + source, + preview, + error, + emptySourcePlaceholder, + errorPreview, + sourcePlaceholder, + } = props; + + // Whether the error has been "committed": shown while the popup was + // closed. The last-good preview only stays up within the editing session + // that introduced the error - once committed, the error state also shows + // when the popup reopens, until the source renders successfully again. + // Updated during render (rather than in an effect) so reopening doesn't + // flash the stale preview for a frame first. + const errorCommittedRef = useRef(false); + if (error == null) { + errorCommittedRef.current = false; + } else if (!popup.isOpen) { + errorCommittedRef.current = true; + } + + // For both placeholder props, a string customizes the default element's + // text while any other element replaces it entirely. + const emptyState = + emptySourcePlaceholder == null || + typeof emptySourcePlaceholder === "string" ? ( + + ) : ( + emptySourcePlaceholder + ); + const errorState = + errorPreview == null || typeof errorPreview === "string" ? ( + + ) : ( + errorPreview + ); + + // Links the source input to the render error so screen readers announce it + // when the input is focused. + const errorId = useId(); + + // What to show in place of the source: the empty state, the preview, or - + // when the source has an error that's committed (or no last-good preview + // to keep showing) - the error state. + const previewContent = + source.length === 0 + ? emptyState + : error != null && (errorCommittedRef.current || preview == null) + ? errorState + : preview; + + // Whether the source is empty and the preview instead shows a button to edit it. + const previewIsButton = + editor.isEditable && + (source.length === 0 || + (error != null && (errorCommittedRef.current || preview == null))); + + // Opens the popup when clicking the preview. + const handlePreviewClick = (event: MouseEvent) => { + if (!editor.isEditable) { + return; + } + + event.stopPropagation(); + + popup.open(); + }; + + // Closes the popup when clicking the "OK" button. + const handleOkButtonClick = (event: MouseEvent) => { + event.stopPropagation(); + + popup.close(); + }; + + // `span` wrappers so inline content stays valid inside a paragraph. + const Wrapper = inline ? "span" : "div"; + const PreviewContainer = inline ? "span" : "div"; + + return ( + + + {previewContent} + +
+
+
+            
+            {inline && source.length === 0 && (
+              
+            )}
+          
+
+ +
+
+ +
+
+ ); +}; diff --git a/packages/react/src/blocks/SourceWithPreview/block/SourceBlockWithPreview.tsx b/packages/react/src/blocks/SourceWithPreview/block/SourceBlockWithPreview.tsx new file mode 100644 index 0000000000..1384b13cda --- /dev/null +++ b/packages/react/src/blocks/SourceWithPreview/block/SourceBlockWithPreview.tsx @@ -0,0 +1,45 @@ +import { ReactCustomBlockRenderProps } from "../../../schema/ReactBlockSpec.js"; +import { + SourceWithPreview, + SourceWithPreviewProps, +} from "../SourceWithPreview.js"; +import { useSourceBlockPreviewPopup } from "./useSourceBlockPreviewPopup.js"; + +export type SourceBlockWithPreviewProps = Pick< + ReactCustomBlockRenderProps, + "block" | "editor" +> & + // `contentRef` comes from the shared props rather than being picked from + // `ReactCustomBlockRenderProps`, as it's conditional on the block's content + // type there, which TypeScript can't resolve for a generic block config. + SourceWithPreviewProps; + +/** + * Renders a block as a preview of its source content, with the editable + * source in a popup. The popup is controlled via + * {@link useSourceBlockPreviewPopup}, so the block's + * `SourceBlockWithPreviewExtension` (from `@blocknote/core`) must be registered + * with the block spec. The caller only provides the preview itself, making this + * the base for custom blocks rendered from source code (math, diagrams, etc). + */ +export const SourceBlockWithPreview = (props: SourceBlockWithPreviewProps) => { + const { block, editor, ...shared } = props; + + const popup = useSourceBlockPreviewPopup({ editor, block }); + + // Mirrors the `SourceBlockWithPreviewExtension` Enter handling: when the + // block uses Enter for hard breaks (multi-line source, e.g. diagrams), + // Enter inserts a newline instead of closing the popup. + const enterSubmits = + editor.schema.blockSpecs[block.type]?.implementation?.meta + ?.hardBreakShortcut !== "enter"; + + return ( + + ); +}; diff --git a/packages/react/src/blocks/SourceWithPreview/block/useSourceBlockPreviewPopup.ts b/packages/react/src/blocks/SourceWithPreview/block/useSourceBlockPreviewPopup.ts new file mode 100644 index 0000000000..f7e83df201 --- /dev/null +++ b/packages/react/src/blocks/SourceWithPreview/block/useSourceBlockPreviewPopup.ts @@ -0,0 +1,57 @@ +import { + BlockNoteEditor, + SourceBlockWithPreviewExtension, +} from "@blocknote/core"; + +import { + useExtension, + useExtensionState, +} from "../../../hooks/useExtension.js"; +import type { SourcePreviewPopup } from "../SourcePreviewPopup.js"; + +/** + * Controls the source popup of a block with a preview, e.g. to open it from a + * custom preview element. A block's popup is toggled explicitly - `open` and + * `close` set a flag, separate from the selection. The popup state itself is + * managed by the `SourceBlockWithPreviewExtension` registered with the block + * spec (so it survives node view re-creation and stays in sync with the + * keyboard handling) - this hook is the React API to it. + */ +export const useSourceBlockPreviewPopup = (props: { + editor: BlockNoteEditor; + block: { id: string }; +}): SourcePreviewPopup => { + const { editor, block } = props; + + const { store } = useExtension(SourceBlockWithPreviewExtension, { editor }); + + const isOpen = useExtensionState(SourceBlockWithPreviewExtension, { + editor, + selector: (state) => state.popupOpen === block.id, + }); + const isSelected = useExtensionState(SourceBlockWithPreviewExtension, { + editor, + selector: (state) => state.selected === block.id, + }); + + // Opens the popup with the cursor at the end of the source. + const open = () => { + if (!editor.isEditable) { + return; + } + + store.setState((state) => ({ ...state, popupOpen: block.id })); + editor.setTextCursorPosition(block.id, "end"); + editor.focus(); + }; + + const close = () => { + store.setState((state) => ({ ...state, popupOpen: undefined })); + // Restores focus in case closing was triggered by clicking the "OK" + // button, which moves focus to it - otherwise keyboard interactions (e.g. + // Enter to re-open the popup) stop reaching the editor. + editor.focus(); + }; + + return { isOpen, isSelected, open, close }; +}; diff --git a/packages/react/src/blocks/SourceWithPreview/inlineContent/SourceInlineContentWithPreview.tsx b/packages/react/src/blocks/SourceWithPreview/inlineContent/SourceInlineContentWithPreview.tsx new file mode 100644 index 0000000000..60a3970758 --- /dev/null +++ b/packages/react/src/blocks/SourceWithPreview/inlineContent/SourceInlineContentWithPreview.tsx @@ -0,0 +1,34 @@ +import { ReactCustomInlineContentRenderProps } from "../../../schema/ReactInlineContentSpec.js"; +import { + SourceWithPreview, + SourceWithPreviewProps, +} from "../SourceWithPreview.js"; +import { useSourceInlineContentPreviewPopup } from "./useSourceInlineContentPreviewPopup.js"; + +export type SourceInlineContentWithPreviewProps = Pick< + ReactCustomInlineContentRenderProps, + "editor" | "node" | "getPos" +> & + SourceWithPreviewProps; + +/** + * Renders inline content as a preview of its source, with the editable source + * in a popup - the inline counterpart of `SourceBlockWithPreview`. The popup + * is controlled via {@link useSourceInlineContentPreviewPopup}, so the inline + * content's `SourceInlineContentWithPreviewExtension` must be registered with + * the inline content spec. Unlike blocks, the popup is open exactly while the + * selection is inside the inline content's source, which is the same + * condition that marks it as selected. + */ +export const SourceInlineContentWithPreview = ( + props: SourceInlineContentWithPreviewProps, +) => { + const { editor, node, getPos, ...shared } = props; + + const popup = useSourceInlineContentPreviewPopup({ editor, node, getPos }); + + // Enter always commits-and-exits inline sources (see + // `SourceInlineContentWithPreviewExtension`), so the default `enterSubmits` + // applies and the "OK" button always shows the return-key icon. + return ; +}; diff --git a/packages/react/src/blocks/SourceWithPreview/inlineContent/useSourceInlineContentPreviewPopup.ts b/packages/react/src/blocks/SourceWithPreview/inlineContent/useSourceInlineContentPreviewPopup.ts new file mode 100644 index 0000000000..30cc81b7a1 --- /dev/null +++ b/packages/react/src/blocks/SourceWithPreview/inlineContent/useSourceInlineContentPreviewPopup.ts @@ -0,0 +1,94 @@ +import { + BlockNoteEditor, + SourceInlineContentWithPreviewExtension, +} from "@blocknote/core"; +import { TextSelection } from "@tiptap/pm/state"; + +import { + useExtension, + useExtensionState, +} from "../../../hooks/useExtension.js"; +import type { SourcePreviewPopup } from "../SourcePreviewPopup.js"; + +/** + * Controls the source popup of inline content with a preview, e.g. to open it + * from a custom preview element. Unlike a block's, the popup is open exactly + * while the selection is inside the inline content's source - so `isOpen` and + * `isSelected` always agree, and `open`/`close` work by moving the selection + * into/out of the source. The popup state itself is managed by the + * `SourceInlineContentWithPreviewExtension` registered with the inline + * content spec (so it survives node view re-creation and stays in sync with + * the keyboard handling) - this hook is the React API to it. + */ +export const useSourceInlineContentPreviewPopup = (props: { + editor: BlockNoteEditor; + node: { nodeSize: number }; + getPos: () => number | undefined; +}): SourcePreviewPopup => { + const { editor, node, getPos } = props; + + const { store } = useExtension(SourceInlineContentWithPreviewExtension, { + editor, + }); + + // `getPos` is called fresh in the selector and actions rather than captured + // once per render, as the inline content's position can shift without its + // node view re-rendering. The `undefined` guard matters when rendered + // outside the editor (i.e. serialized to HTML): there `getPos()` returns + // `undefined`, which must not match the store's initial `undefined` state. + const isSelected = useExtensionState( + SourceInlineContentWithPreviewExtension, + { + editor, + selector: (state) => + state.selected !== undefined && state.selected === getPos(), + }, + ); + + // Opens the popup by moving the selection to the end of the source. + const open = () => { + if (!editor.isEditable) { + return; + } + + const pos = getPos(); + if (!pos) { + return; + } + + store.setState({ selected: pos }); + + const view = editor.prosemirrorView!; + view.dispatch( + view.state.tr.setSelection( + TextSelection.create(view.state.tr.doc, pos + node.nodeSize - 1), + ), + ); + editor.focus(); + }; + + // Closes the popup by moving the selection to just after the inline + // content. + const close = () => { + if (!editor.isEditable) { + return; + } + + const pos = getPos(); + if (!pos) { + return; + } + + const view = editor.prosemirrorView!; + view.dispatch( + view.state.tr.setSelection( + TextSelection.create(view.state.tr.doc, pos + node.nodeSize), + ), + ); + editor.focus(); + }; + + // The popup is open exactly when the selection is inside the source, which + // is the same condition that marks it as selected. + return { isOpen: isSelected, isSelected, open, close }; +}; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2de5361e99..0553f8a30d 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -16,6 +16,13 @@ export * from "./blocks/File/helpers/toExternalHTML/LinkWithCaption.js"; export * from "./blocks/File/useResolveUrl.js"; export * from "./blocks/Image/block.js"; export * from "./blocks/PageBreak/getPageBreakReactSlashMenuItems.js"; +export * from "./blocks/SourceWithPreview/PreviewPlaceholder.js"; +export * from "./blocks/SourceWithPreview/SourceWithPreview.js"; +export * from "./blocks/SourceWithPreview/SourcePreviewPopup.js"; +export * from "./blocks/SourceWithPreview/block/SourceBlockWithPreview.js"; +export * from "./blocks/SourceWithPreview/block/useSourceBlockPreviewPopup.js"; +export * from "./blocks/SourceWithPreview/inlineContent/SourceInlineContentWithPreview.js"; +export * from "./blocks/SourceWithPreview/inlineContent/useSourceInlineContentPreviewPopup.js"; export * from "./blocks/Video/block.js"; export * from "./blocks/ToggleWrapper/ToggleWrapper.js"; diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index 4bd1649292..5311d4e37d 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -33,7 +33,7 @@ export type ReactCustomBlockRenderProps< > = { block: BlockNoDefaults, any, any>; editor: BlockNoteEditor, any, any>; -} & (Config["content"] extends "inline" +} & (Config["content"] extends "inline" | "plain" ? { contentRef: (node: HTMLElement | null) => void; } @@ -63,11 +63,8 @@ export type ReactCustomBlockImplementation< }; export type ReactCustomBlockSpec< - B extends BlockConfig = BlockConfig< - string, - PropSchema, - "inline" | "none" - >, + B extends BlockConfig = + BlockConfig, > = { config: B; implementation: ReactCustomBlockImplementation; @@ -133,7 +130,7 @@ export function BlockContentWrapper< export function createReactBlockSpec< const TName extends string, const TProps extends PropSchema, - const TContent extends "inline" | "none", + const TContent extends "inline" | "none" | "plain", const TOptions extends Record | undefined = undefined, >( blockConfigOrCreator: BlockConfig, @@ -159,7 +156,7 @@ export function createReactBlockSpec< export function createReactBlockSpec< const TName extends string, const TProps extends PropSchema, - const TContent extends "inline" | "none", + const TContent extends "inline" | "none" | "plain", const BlockConf extends BlockConfig, const TOptions extends Partial>, >( @@ -188,7 +185,7 @@ export function createReactBlockSpec< export function createReactBlockSpec< const TName extends string, const TProps extends PropSchema, - const TContent extends "inline" | "none", + const TContent extends "inline" | "none" | "plain", const TOptions extends Record | undefined = undefined, >( blockConfigOrCreator: BlockConfigOrCreator, diff --git a/packages/react/src/schema/ReactInlineContentSpec.tsx b/packages/react/src/schema/ReactInlineContentSpec.tsx index 84df6cefb7..9674a83b81 100644 --- a/packages/react/src/schema/ReactInlineContentSpec.tsx +++ b/packages/react/src/schema/ReactInlineContentSpec.tsx @@ -6,12 +6,15 @@ import { createInternalInlineContentSpec, CustomInlineContentConfig, CustomInlineContentImplementation, + Extension, + ExtensionFactoryInstance, getInlineContentParseRules, InlineContentFromConfig, InlineContentSchemaWithInlineContent, InlineContentSpec, inlineContentToNodes, nodeToCustomInlineContent, + nonFormattingMarks, PartialCustomInlineContentFromConfig, Props, PropSchema, @@ -44,6 +47,16 @@ export type ReactCustomInlineContentRenderProps< S >; contentRef: (node: HTMLElement | null) => void; + /** + * The ProseMirror node backing this inline content. + */ + node: NodeViewProps["node"]; + /** + * Returns this inline content's position in the document. When rendered + * outside the editor (i.e. serialized to HTML) this is a no-op that returns + * `undefined`. + */ + getPos: NodeViewProps["getPos"]; }; // extend BlockConfig but use a React render function @@ -101,10 +114,13 @@ export function InlineContentWrapper< * rendering. * * @param inlineContentConfig - The inline content type configuration, including - * its `type` name, `propSchema`, and `content` mode (`"styled"` or `"none"`). + * its `type` name, `propSchema`, and `content` mode (`"styled"`, `"plain"`, or + * `"none"`). * @param inlineContentImplementation - The React implementation, including a * `render` component and optionally a `toExternalHTML` component and `parse` * rules. + * @param extensions - Optional editor extensions registered alongside this + * inline content (e.g. for keyboard handling), mirroring block specs. * @returns An `InlineContentSpec` that can be passed to the editor's schema. */ export function createReactInlineContentSpec< @@ -114,17 +130,33 @@ export function createReactInlineContentSpec< >( inlineContentConfig: T, inlineContentImplementation: ReactInlineContentImplementation, + extensions?: (Extension | ExtensionFactoryInstance)[], ): InlineContentSpec { const node = Node.create({ name: inlineContentConfig.type as T["type"], inline: true, group: "inline", - selectable: inlineContentConfig.content === "styled", + selectable: inlineContentConfig.content !== "none", atom: inlineContentConfig.content === "none", draggable: inlineContentImplementation.meta?.draggable, + code: inlineContentImplementation.meta?.code, content: (inlineContentConfig.content === "styled" ? "inline*" - : "") as T["content"] extends "styled" ? "inline*" : "", + : inlineContentConfig.content === "plain" + ? "text*" + : "") as T["content"] extends "styled" ? "inline*" : "", + // "plain" inline content holds unstyled text, so it disallows formatting + // marks (mirroring "plain" blocks). It still allows the non-formatting marks + // (comments and suggestions/diffs), which annotate content without changing + // it and are ignored by the content model. `nonFormattingMarks` resolves the + // group only when at least one such mark is registered, so a plain inline + // content in an editor without any of them doesn't reference an empty + // (unknown) mark group. + marks() { + return inlineContentConfig.content === "plain" + ? nonFormattingMarks(this.editor) + : undefined; + }, addAttributes() { return propsToAttributes(inlineContentConfig.propSchema); @@ -138,6 +170,7 @@ export function createReactInlineContentSpec< return getInlineContentParseRules( inlineContentConfig, inlineContentImplementation.parse, + inlineContentImplementation.parseContent, ); }, @@ -166,6 +199,8 @@ export function createReactInlineContentSpec< // No-op }} editor={editor} + node={node} + getPos={() => undefined} /> ), editor, @@ -205,6 +240,8 @@ export function createReactInlineContentSpec< } }} editor={editor} + node={props.node} + getPos={props.getPos} inlineContent={ nodeToCustomInlineContent( props.node, @@ -248,6 +285,12 @@ export function createReactInlineContentSpec< node, render(inlineContent, updateInlineContent, editor) { const Content = inlineContentImplementation.render; + // Rendered outside the editor (serialization), so there's no live node + // view - derive the node from the content and stub out `getPos`. + const node = inlineContentToNodes( + [inlineContent] as any, + editor.pmSchema, + )[0]; const output = renderToDOMSpec((ref) => { return ( undefined} /> ); @@ -275,6 +320,12 @@ export function createReactInlineContentSpec< const Content = inlineContentImplementation.toExternalHTML || inlineContentImplementation.render; + // Rendered outside the editor (serialization), so there's no live node + // view - derive the node from the content and stub out `getPos`. + const node = inlineContentToNodes( + [inlineContent] as any, + editor.pmSchema, + )[0]; const output = renderToDOMSpec((ref) => { return ( { // no-op }} + node={node} + getPos={() => undefined} /> ); @@ -301,5 +354,6 @@ export function createReactInlineContentSpec< return output; }, }, + extensions, ) as any; } diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts index aae55fb97c..2a835469db 100644 --- a/packages/react/vite.config.ts +++ b/packages/react/vite.config.ts @@ -12,7 +12,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/server-util/package.json b/packages/server-util/package.json index 9fd95bad40..ec1c719725 100644 --- a/packages/server-util/package.json +++ b/packages/server-util/package.json @@ -59,7 +59,7 @@ "@blocknote/core": "workspace:^", "@blocknote/react": "workspace:^", "@tiptap/pm": "^3.29.2", - "jsdom": "^25.0.1", + "jsdom": "^29.0.2", "yjs": "^13.6.27" }, "devDependencies": { @@ -72,7 +72,7 @@ "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { diff --git a/packages/server-util/vite.config.ts b/packages/server-util/vite.config.ts index e92f52f422..da43b397e1 100644 --- a/packages/server-util/vite.config.ts +++ b/packages/server-util/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/shadcn/package.json b/packages/shadcn/package.json index 5763809a8b..4e464286e6 100644 --- a/packages/shadcn/package.json +++ b/packages/shadcn/package.json @@ -72,7 +72,7 @@ "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plugin-externalize-deps": "^0.10.0", "vite-plus": "catalog:" }, diff --git a/packages/shadcn/vite.config.ts b/packages/shadcn/vite.config.ts index 303db0121b..75055584af 100644 --- a/packages/shadcn/vite.config.ts +++ b/packages/shadcn/vite.config.ts @@ -12,7 +12,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/xl-ai-server/package.json b/packages/xl-ai-server/package.json index 91047a91f5..ac6e1fe1df 100644 --- a/packages/xl-ai-server/package.json +++ b/packages/xl-ai-server/package.json @@ -57,7 +57,7 @@ "@types/node": "22.13.13", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "undici": "^6.22.0", "vite-node": "^6.0.0", "vite-plugin-externalize-deps": "^0.10.0", diff --git a/packages/xl-ai-server/vite.config.ts b/packages/xl-ai-server/vite.config.ts index 15f535971e..1bb90638b0 100644 --- a/packages/xl-ai-server/vite.config.ts +++ b/packages/xl-ai-server/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/xl-ai/package.json b/packages/xl-ai/package.json index 1c965b2001..46432ad073 100644 --- a/packages/xl-ai/package.json +++ b/packages/xl-ai/package.json @@ -86,7 +86,6 @@ "prosemirror-view": "^1.42.2" }, "devDependencies": { - "@blocknote/shared": "workspace:^", "react-icons": "^5.5.0", "@ai-sdk/anthropic": "^3.0.2", "@ai-sdk/google": "^3.0.2", @@ -111,7 +110,7 @@ "msw-snapshot": "^5.3.0", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "undici": "^6.22.0", "vite-plugin-externalize-deps": "^0.10.0", "vite-plus": "catalog:", diff --git a/packages/xl-ai/vite.config.ts b/packages/xl-ai/vite.config.ts index e488b7d471..8d47bb1287 100644 --- a/packages/xl-ai/vite.config.ts +++ b/packages/xl-ai/vite.config.ts @@ -12,7 +12,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/xl-docx-exporter/package.json b/packages/xl-docx-exporter/package.json index 632f9c68a9..97ba936604 100644 --- a/packages/xl-docx-exporter/package.json +++ b/packages/xl-docx-exporter/package.json @@ -72,9 +72,9 @@ "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", - "xml-formatter": "^3.6.7", - "vite-plus": "catalog:" + "typescript": "^7.0.2", + "vite-plus": "catalog:", + "xml-formatter": "^3.6.7" }, "peerDependencies": { "react": "^18.0 || ^19.0 || >= 19.0.0-rc", diff --git a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts index 77c360b668..87e5c3a5ea 100644 --- a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts +++ b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts @@ -1,9 +1,11 @@ import { + BlockFromConfigNoChildren, BlockMapping, COLORS_DEFAULT, createPageBreakBlockConfig, DefaultBlockSchema, DefaultProps, + PlainContent, UnreachableCaseError, } from "@blocknote/core"; import { multiColumnSchema } from "@blocknote/xl-multi-column"; @@ -24,6 +26,10 @@ import { } from "docx"; import { Table } from "../util/Table.js"; +type BSchema = DefaultBlockSchema & { + pageBreak: ReturnType; +} & typeof multiColumnSchema.blockSchema; + function blockPropsToStyles( props: Partial, colors: typeof COLORS_DEFAULT, @@ -68,10 +74,32 @@ function blockPropsToStyles( })(), }; } + +const codeMapping = ( + block: BlockFromConfigNoChildren, +) => { + // Code blocks hold plain content: at most a single unstyled text item. + const [textItem, ...excessItems] = block.content as PlainContent; + if (excessItems.length > 0 || (textItem && !("text" in textItem))) { + throw new Error("expected plain block content to be a single text item"); + } + const textContent = textItem?.text ?? ""; + + return new Paragraph({ + style: "SourceCode", + children: [ + ...textContent.split("\n").map((line, index) => { + return new TextRun({ + text: line, + break: index > 0 ? 1 : 0, + }); + }), + ], + }); +}; + export const docxBlockMappingForDefaultSchema: BlockMapping< - DefaultBlockSchema & { - pageBreak: ReturnType; - } & typeof multiColumnSchema.blockSchema, + BSchema, any, any, | Promise @@ -145,42 +173,23 @@ export const docxBlockMappingForDefaultSchema: BlockMapping< }, audio: (block, exporter) => { return [ - file(block.props, "Open audio", exporter), + file(block.props, exporter.dictionary.open_audio_file, exporter), ...caption(block.props, exporter), ]; }, video: (block, exporter) => { return [ - file(block.props, "Open video", exporter), + file(block.props, exporter.dictionary.open_video_file, exporter), ...caption(block.props, exporter), ]; }, file: (block, exporter) => { return [ - file(block.props, "Open file", exporter), + file(block.props, exporter.dictionary.open_file, exporter), ...caption(block.props, exporter), ]; }, - codeBlock: (block) => { - // Code blocks hold plain content: at most a single unstyled text item. - const [textItem, ...excessItems] = block.content; - if (excessItems.length > 0 || (textItem && !("text" in textItem))) { - throw new Error("expected plain block content to be a single text item"); - } - const textContent = textItem?.text ?? ""; - - return new Paragraph({ - style: "SourceCode", - children: [ - ...textContent.split("\n").map((line, index) => { - return new TextRun({ - text: line, - break: index > 0 ? 1 : 0, - }); - }), - ], - }); - }, + codeBlock: codeMapping, pageBreak: () => { return new Paragraph({ children: [new PageBreak()], diff --git a/packages/xl-docx-exporter/src/docx/defaultSchema/inlinecontent.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/inlinecontent.ts index 5d8b0f442d..aa783b12d8 100644 --- a/packages/xl-docx-exporter/src/docx/defaultSchema/inlinecontent.ts +++ b/packages/xl-docx-exporter/src/docx/defaultSchema/inlinecontent.ts @@ -6,8 +6,10 @@ import { import { ExternalHyperlink, ParagraphChild, TextRun } from "docx"; import type { DOCXExporter } from "../docxExporter.js"; +type ICSchema = DefaultInlineContentSchema; + export const docxInlineContentMappingForDefaultSchema: InlineContentMapping< - DefaultInlineContentSchema, + ICSchema, DefaultStyleSchema, ParagraphChild, TextRun diff --git a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts index a24340a7ab..53722a4988 100644 --- a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts +++ b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts @@ -3,6 +3,7 @@ import { defaultBlockSpecs, createPageBreakBlockSpec, } from "@blocknote/core"; +import { de } from "@blocknote/core/locales"; import { testDocument } from "@shared/testDocument.js"; import { BlobReader, @@ -59,8 +60,6 @@ describe("exporter", () => { await expect( prettify(await getZIPEntryContent(entries, "word/styles.xml")), ).toMatchFileSnapshot("__snapshots__/basic/styles.xml"); - - // fs.writeFileSync(__dirname + "/My Document.docx", buffer); }); it( @@ -107,8 +106,6 @@ describe("exporter", () => { const blob = await Packer.toBlob(doc); - // fs.writeFileSync(__dirname + "/My Document.docx", buffer); - const zip = new ZipReader(new BlobReader(blob)); const entries = await zip.getEntries(); @@ -245,6 +242,40 @@ describe("exporter", () => { return zip.getEntries(); } + it( + "should export file links with the configured dictionary", + { timeout: 10000 }, + async () => { + // Exporter strings are never hardcoded - the file link text comes + // from the `exporter` section of the configured dictionary (English + // when not configured). + const exporter = new DOCXExporter( + BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + pageBreak: createPageBreakBlockSpec(), + }, + }), + docxDefaultSchemaMappings, + { resolveFileUrl: testResolveFileUrl, dictionary: de }, + ); + const doc = await exporter.toDocxJsDocument(testDocument, { + sectionOptions: {}, + documentOptions: {}, + locale: "de-DE", + }); + + const zip = new ZipReader(new BlobReader(await Packer.toBlob(doc))); + const documentXML = await getZIPEntryContent( + await zip.getEntries(), + "word/document.xml", + ); + + expect(documentXML).toContain("Datei öffnen"); + expect(documentXML).not.toContain("Open file"); + }, + ); + it( "should export a document without w:lang when no locale is provided", { timeout: 10000 }, diff --git a/packages/xl-docx-exporter/vite.config.ts b/packages/xl-docx-exporter/vite.config.ts index 8a88c957f6..65c7f81406 100644 --- a/packages/xl-docx-exporter/vite.config.ts +++ b/packages/xl-docx-exporter/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/xl-email-exporter/package.json b/packages/xl-email-exporter/package.json index 05590fda47..96fd1e5e20 100644 --- a/packages/xl-email-exporter/package.json +++ b/packages/xl-email-exporter/package.json @@ -69,7 +69,7 @@ "react-email": "^5.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { diff --git a/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap b/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap index aa26f51f26..8c8fdbd507 100644 --- a/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap +++ b/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap @@ -1,10 +1,10 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`react email exporter > should export a document (HTML snapshot) > __snapshots__/reactEmailExporter 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; +exports[`react email exporter > should export a document (HTML snapshot) > __snapshots__/reactEmailExporter 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; -exports[`react email exporter > should export a document with multiple preview lines > __snapshots__/reactEmailExporterWithMultiplePreview 1`] = `"
First preview lineSecond preview line
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; +exports[`react email exporter > should export a document with multiple preview lines > __snapshots__/reactEmailExporterWithMultiplePreview 1`] = `"
First preview lineSecond preview line
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; -exports[`react email exporter > should export a document with preview > __snapshots__/reactEmailExporterWithPreview 1`] = `"
This is a preview of the email content
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; +exports[`react email exporter > should export a document with preview > __snapshots__/reactEmailExporterWithPreview 1`] = `"
This is a preview of the email content
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; exports[`react email exporter > should handle document with background colors > __snapshots__/reactEmailExporterBackgroundColor 1`] = `"

Text with background color

"`; @@ -14,7 +14,7 @@ exports[`react email exporter > should handle document with code blocks > __snap exports[`react email exporter > should handle document with complex nested structure > __snapshots__/reactEmailExporterComplexNested 1`] = `"

Complex Document

This is a paragraph with bold and italic text, plus a link.

  • List item with nested content

    Nested paragraph

    1. Nested numbered item

"`; -exports[`react email exporter > should handle document with custom body styles > __snapshots__/reactEmailExporterCustomBodyStyles 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; +exports[`react email exporter > should handle document with custom body styles > __snapshots__/reactEmailExporterCustomBodyStyles 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://placehold.co/332x322.jpg
Open video

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9
const ‍​helloWorld ‍​= ‍​(message) ‍​=> ‍​{
 ‍​ ‍​console.log("Hello World", ‍​message);
};

Some inline code: var foo = 'bar';


All those moments will be lost in time, like tears in rain.

"`; exports[`react email exporter > should handle document with headings of different levels > __snapshots__/reactEmailExporterHeadings 1`] = `"

Heading 1

Heading 2

Heading 3

"`; diff --git a/packages/xl-email-exporter/src/react-email/defaultSchema/blocks.tsx b/packages/xl-email-exporter/src/react-email/defaultSchema/blocks.tsx index f617736b6d..854fec4392 100644 --- a/packages/xl-email-exporter/src/react-email/defaultSchema/blocks.tsx +++ b/packages/xl-email-exporter/src/react-email/defaultSchema/blocks.tsx @@ -1,8 +1,10 @@ import { + BlockFromConfigNoChildren, BlockMapping, createPageBreakBlockConfig, DefaultBlockSchema, mapTableCell, + PlainContent, } from "@blocknote/core"; import { CodeBlock, @@ -114,12 +116,41 @@ export const defaultReactEmailTextStyles = { }, } satisfies ReactEmailTextStyles; +type BSchema = DefaultBlockSchema & { + pageBreak: ReturnType; +}; + +const codeMapping = ( + block: BlockFromConfigNoChildren, + language: PrismLanguage, + textStyles: ReactEmailTextStyles, +) => { + // Code blocks hold plain content: at most a single unstyled text item. + const [textItem, ...excessItems] = block.content as PlainContent; + if (excessItems.length > 0 || (textItem && !("text" in textItem))) { + throw new Error("expected plain block content to be a single text item"); + } + const textContent = textItem?.text ?? ""; + + return ( + + ); +}; + export const createReactEmailBlockMappingForDefaultSchema = ( textStyles: ReactEmailTextStyles = defaultReactEmailTextStyles, ): BlockMapping< - DefaultBlockSchema & { - pageBreak: ReturnType; - }, + BSchema, any, any, React.ReactElement, @@ -258,29 +289,9 @@ export const createReactEmailBlockMappingForDefaultSchema = ( ); }, - codeBlock: (block) => { - // Code blocks hold plain content: at most a single unstyled text item. - const [textItem, ...excessItems] = block.content; - if (excessItems.length > 0 || (textItem && !("text" in textItem))) { - throw new Error("expected plain block content to be a single text item"); - } - const textContent = textItem?.text ?? ""; - - return ( - - ); - }, - audio: (block) => { + codeBlock: (block) => + codeMapping(block, block.props.language as PrismLanguage, textStyles), + audio: (block, exporter) => { // Audio icon SVG const icon = ( ); }, - video: (block) => { + video: (block, exporter) => { // Video icon SVG const icon = ( ); }, - file: (block) => { + file: (block, exporter) => { // File icon SVG const icon = ( | React.ReactElement, React.ReactElement diff --git a/packages/xl-email-exporter/src/react-email/imageDelivery.test.ts b/packages/xl-email-exporter/src/react-email/imageDelivery.test.ts new file mode 100644 index 0000000000..86ff2938e6 --- /dev/null +++ b/packages/xl-email-exporter/src/react-email/imageDelivery.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { + createCIDImageDelivery, + dataURLImageDelivery, +} from "./imageDelivery.js"; + +// Base64 "AAAA" = three zero bytes. +const pngImage = { + mimeType: "image/png", + data: new Uint8Array([0, 0, 0]), + width: 100, + height: 50, +}; + +describe("dataURLImageDelivery", () => { + it("encodes the image as a data URL src", () => { + expect(dataURLImageDelivery.deliver({ ...pngImage, name: "math" })).toBe( + "data:image/png;base64,AAAA", + ); + }); +}); + +describe("createCIDImageDelivery", () => { + it("collects base64 attachments and returns cid: srcs", () => { + const delivery = createCIDImageDelivery(); + + const src = delivery.deliver({ ...pngImage, name: "math" }); + + expect(src).toBe("cid:math-1@blocknote"); + expect(delivery.attachments).toEqual([ + { + cid: "math-1@blocknote", + filename: "math-1.png", + content: "AAAA", + encoding: "base64", + contentType: "image/png", + contentDisposition: "inline", + }, + ]); + }); + + it("numbers multiple images to keep CIDs and filenames unique", () => { + const delivery = createCIDImageDelivery(); + + delivery.deliver({ ...pngImage, name: "math" }); + const second = delivery.deliver({ ...pngImage, name: "diagram" }); + + expect(second).toBe("cid:diagram-2@blocknote"); + expect(delivery.attachments[1].filename).toBe("diagram-2.png"); + }); + + it("converts SVG images, including non-Latin-1 characters", () => { + // MathJax SVG output can contain characters outside Latin-1 (which a + // naive `btoa` rejects); the byte-based contract must round-trip them. + const svg = `e^{iπ} + 1 = 0`; + const delivery = createCIDImageDelivery(); + + const src = delivery.deliver({ + mimeType: "image/svg+xml", + data: new TextEncoder().encode(svg), + width: 100, + height: 20, + name: "math", + }); + + expect(src).toBe("cid:math-1@blocknote"); + const attachment = delivery.attachments[0]; + expect(attachment.contentType).toBe("image/svg+xml"); + expect(attachment.filename).toBe("math-1.svg"); + // The attachment round-trips back to the original SVG. + const bytes = Uint8Array.from(atob(attachment.content), (char) => + char.charCodeAt(0), + ); + expect(new TextDecoder().decode(bytes)).toBe(svg); + }); +}); diff --git a/packages/xl-email-exporter/src/react-email/imageDelivery.ts b/packages/xl-email-exporter/src/react-email/imageDelivery.ts new file mode 100644 index 0000000000..7423ecb400 --- /dev/null +++ b/packages/xl-email-exporter/src/react-email/imageDelivery.ts @@ -0,0 +1,97 @@ +import { + bytesToBase64, + ExportImage, + exportImageToDataURL, +} from "@blocknote/core"; + +/** + * How generated images (math formulas, diagrams) find their way into an + * email. Mappings that generate images take a delivery via their factory + * options, hand it the generated {@link ExportImage}, and use the returned + * string as the `` src. + * + * Custom deliveries can implement any transport: `deliver` registers the + * image and synchronously returns the reference to embed (a data URL, a + * `cid:`, a content-addressed hosted URL, ...); work that can't happen + * during rendering - uploading, attaching - happens after the email is + * rendered, from what was registered (see {@link createCIDImageDelivery} + * for this pattern). + */ +export type ReactEmailImageDelivery = { + /** + * Registers a generated image and returns the `src` to reference it with + * in the email body. Must be synchronous: some inline content renders + * synchronously. + * + * @param image - The generated image, plus a short `name` for the image + * kind (e.g. "math"), used for attachment filenames. + */ + deliver: (image: ExportImage & { name: string }) => string; +}; + +/** + * Embeds images directly in the email body as data URLs. Self-contained (no + * attachments to manage), but some email clients (notably Gmail and Outlook + * for Windows) don't display data URL images. + */ +export const dataURLImageDelivery: ReactEmailImageDelivery = { + deliver: (image) => exportImageToDataURL(image), +}; + +/** + * Delivers images as inline email attachments, referenced from the body via + * `cid:` URLs (RFC 2392) - the most widely supported way to embed generated + * images (works in Gmail and Outlook, which both block data URLs). + * + * Attaching happens at send time: after rendering the email, pass + * `attachments` to your mailer alongside the HTML. The attachment objects + * use the field names of nodemailer & compatible APIs: + * + * ```ts + * const imageDelivery = createCIDImageDelivery(); + * const exporter = new ReactEmailExporter(schema, { + * ...reactEmailDefaultSchemaMappings, + * blockMapping: { + * ...reactEmailDefaultSchemaMappings.blockMapping, + * math: createMathBlockMapping({ imageDelivery }), + * }, + * }); + * const html = await exporter.toReactEmailDocument(blocks); + * + * await transporter.sendMail({ html, attachments: imageDelivery.attachments }); + * ``` + * + * Create one delivery per rendered email - the attachment list accumulates + * across renders otherwise. + */ +export function createCIDImageDelivery(): ReactEmailImageDelivery & { + attachments: { + cid: string; + filename: string; + content: string; + encoding: "base64"; + contentType: string; + contentDisposition: "inline"; + }[]; +} { + const attachments: ReturnType["attachments"] = + []; + + return { + attachments, + deliver: (image) => { + const cid = `${image.name}-${attachments.length + 1}@blocknote`; + const extension = image.mimeType.split("/")[1]?.split("+")[0] ?? "bin"; + attachments.push({ + cid, + filename: `${image.name}-${attachments.length + 1}.${extension}`, + content: bytesToBase64(image.data), + encoding: "base64", + contentType: image.mimeType, + contentDisposition: "inline", + }); + + return `cid:${cid}`; + }, + }; +} diff --git a/packages/xl-email-exporter/src/react-email/index.ts b/packages/xl-email-exporter/src/react-email/index.ts index 8412da0065..b4da61e643 100644 --- a/packages/xl-email-exporter/src/react-email/index.ts +++ b/packages/xl-email-exporter/src/react-email/index.ts @@ -1,2 +1,3 @@ export * from "./defaultSchema/index.js"; +export * from "./imageDelivery.js"; export * from "./reactEmailExporter.jsx"; diff --git a/packages/xl-email-exporter/vite.config.ts b/packages/xl-email-exporter/vite.config.ts index adfc639875..7f66ce760b 100644 --- a/packages/xl-email-exporter/vite.config.ts +++ b/packages/xl-email-exporter/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/xl-multi-column/package.json b/packages/xl-multi-column/package.json index 24f42a0fd5..9369454870 100644 --- a/packages/xl-multi-column/package.json +++ b/packages/xl-multi-column/package.json @@ -62,12 +62,12 @@ "react-icons": "^5.5.0", "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "jsdom": "^25.0.1", + "jsdom": "^29.0.2", "react": "^19.2.5", "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { diff --git a/packages/xl-multi-column/vite.config.ts b/packages/xl-multi-column/vite.config.ts index 5b0d09586a..ef2d51f5e1 100644 --- a/packages/xl-multi-column/vite.config.ts +++ b/packages/xl-multi-column/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, diff --git a/packages/xl-odt-exporter/package.json b/packages/xl-odt-exporter/package.json index 6bc2afb081..8c19e3c449 100644 --- a/packages/xl-odt-exporter/package.json +++ b/packages/xl-odt-exporter/package.json @@ -71,9 +71,9 @@ "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", - "xml-formatter": "^3.6.7", - "vite-plus": "catalog:" + "typescript": "^7.0.2", + "vite-plus": "catalog:", + "xml-formatter": "^3.6.7" }, "peerDependencies": { "react": "^18.0 || ^19.0 || >= 19.0.0-rc", diff --git a/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml b/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml index 22f5bde3d1..e108e17026 100644 --- a/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml +++ b/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml @@ -49,59 +49,34 @@ - - + - + - - - - + - + - + - - - - + - + - - - - - - - - - - - - - - - - - - - + - + - + @@ -145,7 +120,7 @@ - + Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. @@ -266,56 +241,56 @@ Check List Item
- + + + - - - + Wide Cell - + Table Cell - + Table Cell - + Wide Cell - + Table Cell - + Table Cell - + Wide Cell - + Table Cell - + Table Cell @@ -340,7 +315,7 @@ - + @@ -379,70 +354,70 @@ Audio file caption - + Inline Content: - + Styled Text Link - - - - + + + + - + Table Cell 1 - + Table Cell 2 - + Table Cell 3 - + Table Cell 4 - + - + Table Cell Bold 5 - + Table Cell 6 - + Table Cell 7 - + Table Cell 8 - + Table Cell 9 @@ -457,15 +432,15 @@ }; - + Some inline code: - + var foo = 'bar'; - - + + All those moments will be lost in time, like tears in rain. diff --git a/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml b/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml index b3081d8610..aeb491ece1 100644 --- a/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml +++ b/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml @@ -49,59 +49,34 @@ - - + - + - - - - + - + - + - - - - + - + - - - - - - - - - - - - - - - - - - - + - + - + @@ -159,7 +134,7 @@ - + Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. @@ -280,56 +255,56 @@ Check List Item - + + + - - - + Wide Cell - + Table Cell - + Table Cell - + Wide Cell - + Table Cell - + Table Cell - + Wide Cell - + Table Cell - + Table Cell @@ -354,7 +329,7 @@ - + @@ -393,70 +368,70 @@ Audio file caption - + Inline Content: - + Styled Text Link - - - - + + + + - + Table Cell 1 - + Table Cell 2 - + Table Cell 3 - + Table Cell 4 - + - + Table Cell Bold 5 - + Table Cell 6 - + Table Cell 7 - + Table Cell 8 - + Table Cell 9 @@ -471,15 +446,15 @@ }; - + Some inline code: - + var foo = 'bar'; - - + + All those moments will be lost in time, like tears in rain. diff --git a/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml b/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml index fd41c45fa7..efe542c994 100644 --- a/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml +++ b/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml @@ -19,66 +19,55 @@ - - - - - - - - - - + + - + - + - - - - - - - + + + + This paragraph is in a column! - + So is this heading! - + You can have multiple blocks in a column too - + Block 1 - + Block 2 - + Block 3 diff --git a/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx b/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx index 7b4d9dcedf..f86ab23d63 100644 --- a/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx +++ b/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx @@ -1,15 +1,45 @@ import { BlockFromConfig, + BlockFromConfigNoChildren, BlockMapping, createPageBreakBlockConfig, DefaultBlockSchema, DefaultProps, mapTableCell, + PlainContent, TableCell, } from "@blocknote/core"; import { multiColumnSchema } from "@blocknote/xl-multi-column"; import { ODTExporter } from "../odtExporter.js"; +type BSchema = DefaultBlockSchema & { + pageBreak: ReturnType; +} & typeof multiColumnSchema.blockSchema; + +const codeMapping = ( + block: BlockFromConfigNoChildren, +) => { + // Code blocks hold plain content: at most a single unstyled text item. + const [textItem, ...excessItems] = block.content as PlainContent; + if (excessItems.length > 0 || (textItem && !("text" in textItem))) { + throw new Error("expected plain block content to be a single text item"); + } + const textContent = textItem?.text ?? ""; + + return ( + + {...textContent.split("\n").map((line, index) => { + return ( + <> + {index !== 0 && } + {line} + + ); + })} + + ); +}; + export const getTabs = (nestingLevel: number) => { return Array.from({ length: nestingLevel }, (_, i) => ); }; @@ -163,9 +193,7 @@ const wrapWithLists = ( }; export const odtBlockMappingForDefaultSchema: BlockMapping< - DefaultBlockSchema & { - pageBreak: ReturnType; - } & typeof multiColumnSchema.blockSchema, + BSchema, any, any, React.ReactNode, @@ -498,30 +526,9 @@ export const odtBlockMappingForDefaultSchema: BlockMapping< ); }, + codeBlock: codeMapping, - codeBlock: (block) => { - // Code blocks hold plain content: at most a single unstyled text item. - const [textItem, ...excessItems] = block.content; - if (excessItems.length > 0 || (textItem && !("text" in textItem))) { - throw new Error("expected plain block content to be a single text item"); - } - const textContent = textItem?.text ?? ""; - - return ( - - {...textContent.split("\n").map((line, index) => { - return ( - <> - {index !== 0 && } - {line} - - ); - })} - - ); - }, - - file: async (block) => { + file: async (block, exporter) => { return ( <> @@ -534,11 +541,11 @@ export const odtBlockMappingForDefaultSchema: BlockMapping< xlink:href={block.props.url} > - Open file + {exporter.dictionary.open_file} ) : ( - "Open file" + exporter.dictionary.open_file )} {block.props.caption && ( @@ -548,7 +555,7 @@ export const odtBlockMappingForDefaultSchema: BlockMapping< ); }, - video: (block) => ( + video: (block, exporter) => ( <> - Open video + + {exporter.dictionary.open_video_file} + {block.props.caption && ( @@ -567,7 +576,7 @@ export const odtBlockMappingForDefaultSchema: BlockMapping< ), - audio: (block) => ( + audio: (block, exporter) => ( <> - Open audio + + {exporter.dictionary.open_audio_file} + {block.props.caption && ( diff --git a/packages/xl-odt-exporter/src/odt/defaultSchema/inlineContent.tsx b/packages/xl-odt-exporter/src/odt/defaultSchema/inlineContent.tsx index 94f0fcaae4..544b1b2d63 100644 --- a/packages/xl-odt-exporter/src/odt/defaultSchema/inlineContent.tsx +++ b/packages/xl-odt-exporter/src/odt/defaultSchema/inlineContent.tsx @@ -3,11 +3,15 @@ import { InlineContentMapping, } from "@blocknote/core"; +type ICSchema = DefaultInlineContentSchema; + +// `React.ReactNode` result types, matching `ODTExporter`'s `Exporter` +// generics - mismatched result types make the mappings unassignable. export const odtInlineContentMappingForDefaultSchema: InlineContentMapping< - DefaultInlineContentSchema, + ICSchema, any, - React.JSX.Element, - React.JSX.Element + React.ReactNode, + React.ReactNode > = { link: (ic, exporter) => { const content = ic.content.map((c) => exporter.transformStyledText(c)); diff --git a/packages/xl-odt-exporter/src/odt/index.ts b/packages/xl-odt-exporter/src/odt/index.ts index 3fadfc7a6a..f4a1c2c0c4 100644 --- a/packages/xl-odt-exporter/src/odt/index.ts +++ b/packages/xl-odt-exporter/src/odt/index.ts @@ -1,2 +1,3 @@ export * from "./defaultSchema/index.js"; export * from "./odtExporter.js"; +export * from "./util/createODTImageParagraph.js"; diff --git a/packages/xl-odt-exporter/src/odt/odtExporter.test.ts b/packages/xl-odt-exporter/src/odt/odtExporter.test.ts index 971d12ba1e..56685b77bd 100644 --- a/packages/xl-odt-exporter/src/odt/odtExporter.test.ts +++ b/packages/xl-odt-exporter/src/odt/odtExporter.test.ts @@ -3,10 +3,10 @@ import { createPageBreakBlockSpec, defaultBlockSpecs, } from "@blocknote/core"; +import { testODTDocumentAgainstSnapshot } from "@shared/util/odtTestUtil.js"; import { testDocument } from "@shared/testDocument.js"; -import { BlobReader, FileEntry, TextWriter, ZipReader } from "@zip.js/zip.js"; import { beforeAll, describe, expect, it } from "vite-plus/test"; -import xmlFormat from "xml-formatter"; +import { createElement } from "react"; import { odtDefaultSchemaMappings } from "./defaultSchema/index.js"; import { ODTExporter } from "./odtExporter.js"; import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; @@ -146,32 +146,35 @@ describe("exporter", () => { }); }, ); -}); -async function testODTDocumentAgainstSnapshot( - odt: globalThis.Blob, - snapshots: { - styles: string; - content: string; - }, -) { - const zipReader = new ZipReader(new BlobReader(odt)); - const entries = await zipReader.getEntries(); - const stylesXMLWriter = new TextWriter(); - const contentXMLWriter = new TextWriter(); - const stylesXML = entries.find( - (entry) => entry.filename === "styles.xml", - ) as FileEntry; - const contentXML = entries.find((entry) => { - return entry.filename === "content.xml"; - }) as FileEntry; + it("deduplicates identical automatic styles", () => { + const exporter = new ODTExporter( + BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + pageBreak: createPageBreakBlockSpec(), + }, + }), + odtDefaultSchemaMappings, + { resolveFileUrl: testResolveFileUrl }, + ); + + const italic = (name: string) => + createElement( + "style:style", + { "style:family": "text", "style:name": name }, + createElement("style:text-properties", { "fo:font-style": "italic" }), + ); + const bold = (name: string) => + createElement( + "style:style", + { "style:family": "text", "style:name": name }, + createElement("style:text-properties", { "fo:font-weight": "bold" }), + ); - expect(stylesXML).toBeDefined(); - expect(contentXML).toBeDefined(); - await expect( - xmlFormat(await stylesXML.getData(stylesXMLWriter)), - ).toMatchFileSnapshot(snapshots.styles); - await expect( - xmlFormat(await contentXML.getData(contentXMLWriter)), - ).toMatchFileSnapshot(snapshots.content); -} + expect(exporter.registerStyle(italic)).toBe(exporter.registerStyle(italic)); + expect(exporter.registerStyle(italic)).not.toBe( + exporter.registerStyle(bold), + ); + }); +}); diff --git a/packages/xl-odt-exporter/src/odt/odtExporter.tsx b/packages/xl-odt-exporter/src/odt/odtExporter.tsx index de9567c59e..7c17cad0ad 100644 --- a/packages/xl-odt-exporter/src/odt/odtExporter.tsx +++ b/packages/xl-odt-exporter/src/odt/odtExporter.tsx @@ -44,7 +44,16 @@ export class ODTExporter< } >(); + // Embedded object sub-documents (e.g. formulas), added as + // "Object N/content.xml" entries in the ODT file. + private objects: Array<{ + path: string; + contentXml: string; + mediaType: string; + }> = []; + private styleCounter = 0; + private readonly registeredStyleNames = new Map(); public readonly options: ExporterOptions; @@ -99,15 +108,22 @@ export class ODTExporter< return styledText.text; } - const styleName = `BN_T${++this.styleCounter}`; - - // Store the complete style element - this.automaticStyles.set( - styleName, - - - , - ); + // Like `registerStyle`, identical style combinations are deduplicated - + // every styled run (each bold word, say) would otherwise create its own + // automatic style. The key is prefixed so the two key spaces can't + // collide. + const key = "T:" + JSON.stringify(styles); + let styleName = this.registeredStyleNames.get(key); + if (styleName === undefined) { + styleName = `BN_T${++this.styleCounter}`; + this.automaticStyles.set( + styleName, + + + , + ); + this.registeredStyleNames.set(key, styleName); + } return {styledText.text}; } @@ -290,6 +306,18 @@ export class ODTExporter< /> ); })} + {this.objects.flatMap((object) => [ + , + , + ])} ); const zipWriter = new ZipWriter( @@ -323,16 +351,56 @@ export class ODTExporter< new BlobReader(picture.file), ); }); + this.objects.forEach((object) => { + void zipWriter.add( + `${object.path}content.xml`, + new TextReader(object.contentXml), + ); + }); return zipWriter.close(); } public registerStyle(style: (name: string) => React.ReactNode): string { + // Identical definitions are deduplicated: mappings register their styles + // per block, and a document with many alike blocks would otherwise fill + // the automatic styles with copies. The definition is keyed by its + // rendered shape, with a placeholder where the generated name appears. + const key = "S:" + JSON.stringify(style("BN_STYLE_NAME_PLACEHOLDER")); + const existing = this.registeredStyleNames.get(key); + if (existing !== undefined) { + return existing; + } const styleName = `BN_S${++this.styleCounter}`; this.automaticStyles.set(styleName, style(styleName)); + this.registeredStyleNames.set(key, styleName); return styleName; } + /** + * Registers an embedded object sub-document (e.g. a formula) and returns + * its path, for referencing from a `draw:object`'s `xlink:href`: + * + * ```tsx + * + * + * + * ``` + */ + public registerObject( + contentXml: string, + mediaType = "application/vnd.oasis.opendocument.formula", + ): string { + const path = `Object ${this.objects.length + 1}/`; + this.objects.push({ path, contentXml, mediaType }); + return path; + } + public async registerPicture(url: string): Promise<{ path: string; mimeType: string; diff --git a/packages/xl-odt-exporter/src/odt/util/createODTImageParagraph.tsx b/packages/xl-odt-exporter/src/odt/util/createODTImageParagraph.tsx new file mode 100644 index 0000000000..e3008cc342 --- /dev/null +++ b/packages/xl-odt-exporter/src/odt/util/createODTImageParagraph.tsx @@ -0,0 +1,68 @@ +import { ODTExporter } from "../odtExporter.js"; + +/** + * Registers a picture (from a URL or data URL) with the exporter and returns + * a paragraph embedding it, sized to the given dimensions (or the image's own + * by default) and aligned as requested. Lets custom block mappings embed + * images without having to build the ODT exporter's internal XML JSX elements + * themselves. + */ +export const createODTImageParagraph = async ( + exporter: ODTExporter, + url: string, + options?: { + /** + * Dimensions to render the image at. Defaults to the image's own. + */ + width?: number; + height?: number; + /** + * Horizontal alignment of the image within the paragraph. + * + * @default "left" + */ + align?: "left" | "center" | "right"; + }, +): Promise => { + const { path, mimeType, ...originalDimensions } = + await exporter.registerPicture(url); + const width = options?.width ?? originalDimensions.width; + const height = options?.height ?? originalDimensions.height; + + const align = options?.align ?? "left"; + const styleName = + align === "left" + ? "Standard" + : exporter.registerStyle((name) => ( + + + + )); + + return ( + + + + + + ); +}; diff --git a/packages/xl-odt-exporter/src/odt/util/jsx.d.ts b/packages/xl-odt-exporter/src/odt/util/jsx.d.ts index 8f996ea6df..5d89aaa19e 100644 --- a/packages/xl-odt-exporter/src/odt/util/jsx.d.ts +++ b/packages/xl-odt-exporter/src/odt/util/jsx.d.ts @@ -22,6 +22,7 @@ declare module "react/jsx-runtime" { "text:tab": any; "draw:frame": any; "draw:image": any; + "draw:object": any; "draw:text-box": any; "table:table": any; "table:table-row": any; @@ -30,6 +31,7 @@ declare module "react/jsx-runtime" { "manifest:manifest": any; "manifest:file-entry": any; "style:paragraph-properties": any; + "style:graphic-properties": any; "style:background-fill": any; "style:table-properties": any; "style:table-cell-properties": any; diff --git a/packages/xl-odt-exporter/vite.config.ts b/packages/xl-odt-exporter/vite.config.ts index e495c78429..78c7ecce14 100644 --- a/packages/xl-odt-exporter/vite.config.ts +++ b/packages/xl-odt-exporter/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, @@ -50,9 +50,16 @@ export default defineConfig( build: { sourcemap: true, lib: { - entry: path.resolve(__dirname, "src/index.ts"), + entry: { + "blocknote-xl-odt-exporter": path.resolve( + __dirname, + "src/index.ts", + ), + }, name: "blocknote-xl-odt-exporter", - fileName: "blocknote-xl-odt-exporter", + formats: ["es", "cjs"], + fileName: (format, entryName) => + format === "es" ? `${entryName}.js` : `${entryName}.cjs`, }, rollupOptions: { // make sure to externalize deps that shouldn't be bundled diff --git a/packages/xl-pdf-exporter/package.json b/packages/xl-pdf-exporter/package.json index 35a1e9e229..5468fa27af 100644 --- a/packages/xl-pdf-exporter/package.json +++ b/packages/xl-pdf-exporter/package.json @@ -58,23 +58,19 @@ "dependencies": { "@blocknote/core": "workspace:^", "@blocknote/xl-multi-column": "workspace:^", - "@react-pdf/renderer": "^4.3.0" + "@react-pdf/renderer": "^4.5.1" }, "devDependencies": { "@blocknote/shared": "workspace:^", "@testing-library/react": "^16.3.0", - "@types/jest-image-snapshot": "^6.4.0", - "@types/jsdom": "^21.1.7", "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "jest-image-snapshot": "^6.5.1", - "pdf-to-img": "^4.5.0", "react": "^19.2.5", "react-dom": "^19.2.5", "react-element-to-jsx-string": "^17.0.1", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", - "typescript": "^5.9.3", + "typescript": "^7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx index 71684acf42..723124df6e 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx @@ -712,7 +712,7 @@ - Open video file + Open video
@@ -755,7 +755,7 @@ - Open audio file + Open audio diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx index dc11a90c93..7281749d28 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx @@ -720,7 +720,7 @@ - Open video file + Open video @@ -763,7 +763,7 @@ - Open audio file + Open audio diff --git a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx index fc1b91a9e1..44ff2b73e0 100644 --- a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx +++ b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx @@ -1,8 +1,10 @@ import { + BlockFromConfigNoChildren, BlockMapping, + createPageBreakBlockConfig, DefaultBlockSchema, DefaultProps, - createPageBreakBlockConfig, + PlainContent, } from "@blocknote/core"; import { multiColumnSchema } from "@blocknote/xl-multi-column"; import { Image, Link, Path, Svg, Text, View } from "@react-pdf/renderer"; @@ -18,10 +20,53 @@ import { Table } from "../util/table/Table.js"; const PIXELS_PER_POINT = 0.75; const FONT_SIZE = 16; +type BSchema = DefaultBlockSchema & { + pageBreak: ReturnType; +} & typeof multiColumnSchema.blockSchema; + +const codeMapping = ( + block: BlockFromConfigNoChildren, +) => { + // Code blocks hold plain content: at most a single unstyled text item. + const [textItem, ...excessItems] = block.content as PlainContent; + if (excessItems.length > 0 || (textItem && !("text" in textItem))) { + throw new Error("expected plain block content to be a single text item"); + } + const textContent = textItem?.text ?? ""; + const lines = textContent.split("\n").map((line, index) => { + const indent = line.match(/^\s*/)?.[0].length || 0; + + return ( + + {line.trimStart() || <> } + + ); + }); + + return ( + + {lines} + + ); +}; + export const pdfBlockMappingForDefaultSchema: BlockMapping< - DefaultBlockSchema & { - pageBreak: ReturnType; - } & typeof multiColumnSchema.blockSchema, + BSchema, any, any, React.ReactElement, @@ -112,44 +157,7 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< ); }, - codeBlock: (block) => { - // Code blocks hold plain content: at most a single unstyled text item. - const [textItem, ...excessItems] = block.content; - if (excessItems.length > 0 || (textItem && !("text" in textItem))) { - throw new Error("expected plain block content to be a single text item"); - } - const textContent = textItem?.text ?? ""; - const lines = textContent.split("\n").map((line, index) => { - const indent = line.match(/^\s*/)?.[0].length || 0; - - return ( - - {line.trimStart() || <> } - - ); - }); - - return ( - - {lines} - - ); - }, + codeBlock: codeMapping, pageBreak: () => { return ; }, @@ -191,7 +199,7 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< {file( block.props, - "Open audio file", + exporter.dictionary.open_audio_file, , @@ -206,7 +214,7 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< {file( block.props, - "Open video file", + exporter.dictionary.open_video_file, , @@ -221,7 +229,7 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< {file( block.props, - "Open file", + exporter.dictionary.open_file, , diff --git a/packages/xl-pdf-exporter/src/pdf/defaultSchema/inlinecontent.tsx b/packages/xl-pdf-exporter/src/pdf/defaultSchema/inlinecontent.tsx index a1ea3cc7e2..064953a2f3 100644 --- a/packages/xl-pdf-exporter/src/pdf/defaultSchema/inlinecontent.tsx +++ b/packages/xl-pdf-exporter/src/pdf/defaultSchema/inlinecontent.tsx @@ -4,8 +4,10 @@ import { } from "@blocknote/core"; import { Link, Text } from "@react-pdf/renderer"; +type ICSchema = DefaultInlineContentSchema; + export const pdfInlineContentMappingForDefaultSchema: InlineContentMapping< - DefaultInlineContentSchema, + ICSchema, any, React.ReactElement | React.ReactElement, React.ReactElement diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx index 738e931a1e..11d0192c17 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx @@ -16,16 +16,9 @@ import { describe, expect, it } from "vite-plus/test"; import { pdfDefaultSchemaMappings } from "./defaultSchema/index.js"; import { PDFExporter } from "./pdfExporter.js"; import { partialBlocksToBlocksForTesting } from "@shared/formatConversionTestUtil.js"; -// import * as ReactPDF from "@react-pdf/renderer"; -// expect.extend({ toMatchImageSnapshot }); -// import { toMatchImageSnapshot } from "jest-image-snapshot"; -// import { pdf } from "pdf-to-img"; describe("exporter", () => { it("typescript: schema with extra block", async () => { - // const exporter = createPdfExporterForDefaultSchema(); - // const ps = exporter.transform(testDocument); - const schema = BlockNoteSchema.create({ blockSpecs: { ...defaultBlockSpecs, @@ -177,24 +170,11 @@ describe("exporter", () => { await expect(str).toMatchFileSnapshot("__snapshots__/example.jsx"); - // would be nice to compare pdf images, but currently doesn't work on mac os (due to node canvas installation issue) - - // await ReactPDF.render(transformed, `${__dirname}/example.pdf`); - // eslint-disable-next-line - // const b = await ReactPDF(transformed); - - // await toMatchBinaryFileSnapshot(b, `__snapshots__/example.pdf`); - // expect(b.toString("utf-8")).toMatchFileSnapshot( - // `__snapshots__/example.pdf` - // ); - // const doc = await pdf(`${__dirname}/example.pdf`); - - // // expect(doc.length).toBe(2); - // // expect(doc.metadata).toEqual({ ... }); - - // for await (const page of doc) { - // expect(page).toMatchImageSnapshot(); - // } + // Visual verification of an actually produced PDF lives in the browser + // suite (tests/src/end-to-end/exporters/exporterImages.test.tsx), which + // renders the file's pages with pdf.js and screenshots them - possible + // there because a real browser needs no native canvas dependencies, + // which is what blocked doing this in Node. }); it("should export a document with header and footer", async () => { @@ -218,11 +198,6 @@ describe("exporter", () => { await expect(str).toMatchFileSnapshot( "__snapshots__/exampleWithHeaderAndFooter.jsx", ); - - // await ReactPDF.render( - // transformed, - // `${__dirname}/exampleWithHeaderAndFooter.pdf` - // ); }); it("should export a document with a multi-column block", async () => { const schema = BlockNoteSchema.create({ diff --git a/packages/xl-pdf-exporter/vite.config.ts b/packages/xl-pdf-exporter/vite.config.ts index 66fcb084de..50022dfd8a 100644 --- a/packages/xl-pdf-exporter/vite.config.ts +++ b/packages/xl-pdf-exporter/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, @@ -21,7 +21,7 @@ export default defineConfig( }, }, test: { - environment: "jsdom", + environment: "node", setupFiles: ["./vitestSetup.ts"], testTimeout: 15000, // assetsInclude: [ diff --git a/patches/katex@0.16.47.patch b/patches/katex@0.16.47.patch new file mode 100644 index 0000000000..3aa9ca48f8 --- /dev/null +++ b/patches/katex@0.16.47.patch @@ -0,0 +1,44 @@ +diff --git a/dist/katex.js b/dist/katex.js +index 2de2003251a8e5dda149c3b707eaf28f42b459f2..e7e92bdbf7c67492119cb76f1eb2d608e3d568ec 100644 +--- a/dist/katex.js ++++ b/dist/katex.js +@@ -14624,7 +14624,7 @@ const functions = _functions; + */ + const spaceRegexString = "[ \r\n\t]"; + const controlWordRegexString = "\\\\[a-zA-Z@]+"; +-const controlSymbolRegexString = "\\\\[^\uD800-\uDFFF]"; ++const controlSymbolRegexString = "\\\\[^\\uD800-\\uDFFF]"; + const controlWordWhitespaceRegexString = "(" + controlWordRegexString + ")" + spaceRegexString + "*"; + const controlSpaceRegexString = "\\\\(\n|[ \r\t]+\n?)[ \r\t]*"; + const combiningDiacriticalMarkString = "[\u0300-\u036f]"; +@@ -14635,7 +14635,7 @@ controlSpaceRegexString + "|") + + "([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + (// single codepoint + combiningDiacriticalMarkString + "*") + + // ...plus accents +-"|[\uD800-\uDBFF][\uDC00-\uDFFF]" + (// surrogate pair ++"|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]" + (// surrogate pair + combiningDiacriticalMarkString + "*") + + // ...plus accents + "|\\\\verb\\*([^]).*?\\4" + +diff --git a/dist/katex.mjs b/dist/katex.mjs +index b4a83b4b7b9b31064413fa7eb5447823110d7305..dc61e4c7d080560cd1b198fba3cbee86f27fb14d 100644 +--- a/dist/katex.mjs ++++ b/dist/katex.mjs +@@ -13512,7 +13512,7 @@ var functions = _functions; + */ + var spaceRegexString = "[ \r\n\t]"; + var controlWordRegexString = "\\\\[a-zA-Z@]+"; +-var controlSymbolRegexString = "\\\\[^\uD800-\uDFFF]"; ++var controlSymbolRegexString = "\\\\[^\\uD800-\\uDFFF]"; + var controlWordWhitespaceRegexString = "(" + controlWordRegexString + ")" + spaceRegexString + "*"; + var controlSpaceRegexString = "\\\\(\n|[ \r\t]+\n?)[ \r\t]*"; + var combiningDiacriticalMarkString = "[\u0300-\u036f]"; +@@ -13523,7 +13523,7 @@ controlSpaceRegexString + "|") + + "([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + (// single codepoint + combiningDiacriticalMarkString + "*") + + // ...plus accents +-"|[\uD800-\uDBFF][\uDC00-\uDFFF]" + (// surrogate pair ++"|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]" + (// surrogate pair + combiningDiacriticalMarkString + "*") + + // ...plus accents + "|\\\\verb\\*([^]).*?\\4" + diff --git a/playground/package.json b/playground/package.json index 481a8dc8ca..dcf6ca2868 100644 --- a/playground/package.json +++ b/playground/package.json @@ -7,7 +7,8 @@ "dev": "vp dev --host", "preview": "vp preview", "lint": "vp lint src", - "clean": "rimraf dist" + "clean": "rimraf dist", + "build:vercel": "cd .. && vp_build() { pnpm exec vp run --verbose --concurrency-limit 1 --filter '@blocknote/example-editor...' build; }; diag() { echo \"[diag] $1 pids.current=$(cat /sys/fs/cgroup/pids.current 2>/dev/null) pids.max=$(cat /sys/fs/cgroup/pids.max 2>/dev/null) mem=$(cat /sys/fs/cgroup/memory.current 2>/dev/null)/$(cat /sys/fs/cgroup/memory.max 2>/dev/null) procs=$(ps -e 2>/dev/null | wc -l)\"; }; vp_build || { diag fail-1; echo '[diag] Vercel builders intermittently fail spawns (host-side EAGAIN); retrying on warm cache...'; sleep 15; vp_build; } || { diag fail-2; sleep 60; vp_build; } || { diag fail-3; sleep 180; vp_build; }" }, "dependencies": { "@ai-sdk/groq": "^3.0.2", @@ -17,6 +18,8 @@ "@blocknote/code-block": "workspace:^", "@blocknote/core": "workspace:^", "@blocknote/mantine": "workspace:^", + "@blocknote/math-block": "workspace:^", + "@blocknote/diagram-block": "workspace:^", "@blocknote/react": "workspace:^", "@blocknote/server-util": "workspace:^", "@blocknote/shadcn": "workspace:^", @@ -26,6 +29,8 @@ "@blocknote/xl-multi-column": "workspace:^", "@blocknote/xl-odt-exporter": "workspace:^", "@blocknote/xl-pdf-exporter": "workspace:^", + "@react-pdf/math": "^2.0.1", + "katex": "^0.16.11", "@emotion/react": "11.14.0", "@emotion/styled": "11.14.1", "@liveblocks/client": "^3.17.0", diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index c0eb48c336..155a460786 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -1020,11 +1020,11 @@ export const examples = { tags: ["Basic"], dependencies: { "@blocknote/code-block": "latest", - "@shikijs/core": "^4", - "@shikijs/engine-javascript": "^4", - "@shikijs/langs-precompiled": "^4", - "@shikijs/themes": "^4", - "@shikijs/types": "^4", + "@shikijs/core": "^4.4.3", + "@shikijs/engine-javascript": "^4.4.3", + "@shikijs/langs-precompiled": "^4.4.3", + "@shikijs/themes": "^4.4.3", + "@shikijs/types": "^4.4.3", } as any, }, title: "Custom Code Block Theme & Language", @@ -1127,9 +1127,13 @@ export const examples = { author: "yousefed", tags: ["Interoperability"], dependencies: { - "@blocknote/xl-pdf-exporter": "latest", + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-multi-column": "latest", - "@react-pdf/renderer": "^4.3.0", + "@blocknote/xl-pdf-exporter": "latest", + "@react-pdf/math": "^2.0.1", + "@react-pdf/renderer": "^4.5.1", + "mathjax-full": "^3.2.2", } as any, pro: true, }, @@ -1152,8 +1156,11 @@ export const examples = { author: "yousefed", tags: [""], dependencies: { + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-docx-exporter": "latest", "@blocknote/xl-multi-column": "latest", + katex: "^0.16.11", } as any, pro: true, }, @@ -1176,8 +1183,11 @@ export const examples = { author: "areknawo", tags: [""], dependencies: { - "@blocknote/xl-odt-exporter": "latest", + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-multi-column": "latest", + "@blocknote/xl-odt-exporter": "latest", + katex: "^0.16.11", } as any, pro: true, }, @@ -1200,6 +1210,8 @@ export const examples = { author: "jmarbutt", tags: [""], dependencies: { + "@blocknote/diagram-block": "latest", + "@blocknote/math-block": "latest", "@blocknote/xl-email-exporter": "latest", "@react-email/render": "^2.0.4", } as any, @@ -1445,6 +1457,85 @@ export const examples = { readme: "In this example, we create a custom block which renders a simple HTML paragraph with placeholder text. The block has no editable content.\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)", }, + { + projectSlug: "math-block", + fullSlug: "custom-schema/math-block", + pathFromRoot: "examples/06-custom-schema/09-math-block", + config: { + playground: true, + docs: true, + author: "matthewlipski", + tags: [ + "Intermediate", + "Blocks", + "Custom Schemas", + "Suggestion Menus", + "Slash Menu", + ], + dependencies: { + "@blocknote/code-block": "latest", + "@blocknote/math-block": "latest", + "react-icons": "^5.5.0", + } as any, + }, + title: "Math Block", + group: { + pathFromRoot: "examples/06-custom-schema", + slug: "custom-schema", + }, + readme: + "In this example, we register the `@blocknote/math-block` block in a custom schema. The math block renders LaTeX as MathML (using Temml) for the browser to display natively, and reveals an editable LaTeX source popup when selected. Exporting to HTML produces a MathML `` element, and pasting MathML back in is converted to LaTeX.\n\n**Try it out:** Click a formula to edit its LaTeX!\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)", + }, + { + projectSlug: "diagram-block", + fullSlug: "custom-schema/diagram-block", + pathFromRoot: "examples/06-custom-schema/10-diagram-block", + config: { + playground: true, + docs: true, + author: "yousefed", + tags: [ + "Intermediate", + "Blocks", + "Custom Schemas", + "Suggestion Menus", + "Slash Menu", + ], + dependencies: { + "@blocknote/code-block": "latest", + "@blocknote/diagram-block": "latest", + "react-icons": "^5.5.0", + } as any, + }, + title: "Diagram Block", + group: { + pathFromRoot: "examples/06-custom-schema", + slug: "custom-schema", + }, + readme: + "In this example, we register the `@blocknote/diagram-block` block in a custom schema. The block renders diagrams from [Mermaid](https://mermaid.js.org/) source code, showing the rendered diagram in place of the source and revealing an editable source popup when selected - built from the same `SourceBlockWithPreview` component the math block uses, so the block itself is only a few dozen lines.\n\n**Try it out:** Click a diagram to edit its Mermaid source!\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)", + }, + { + projectSlug: "source-with-preview", + fullSlug: "custom-schema/source-with-preview", + pathFromRoot: "examples/06-custom-schema/11-source-with-preview", + config: { + playground: true, + docs: true, + author: "yousefed", + tags: ["Advanced", "Blocks", "Custom Schemas"], + dependencies: { + "react-icons": "^5.5.0", + } as any, + }, + title: "Source with Preview Blocks", + group: { + pathFromRoot: "examples/06-custom-schema", + slug: "custom-schema", + }, + readme: + 'In this example, we build custom blocks on the source-with-preview pattern — the same building blocks behind BlockNote\'s math and diagram blocks. A custom "CSV table" block renders its comma-separated source as a table, and a custom "color" inline content renders a CSS color as a swatch. Both show the rendered preview in place, while the source is edited in a popup.\n\n**Try it out:** Click the table or a color chip to edit its source!\n\n**Relevant Docs:**\n\n- [Source with Preview Blocks](/docs/features/custom-schemas/source-with-preview)\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Custom Inline Content](/docs/features/custom-schemas/custom-inline-content)', + }, { projectSlug: "draggable-inline-content", fullSlug: "custom-schema/draggable-inline-content", @@ -1822,7 +1913,6 @@ export const examples = { author: "yousefed", tags: ["Advanced", "Development", "Collaboration"], dependencies: { - "@blocknote/shared": "latest", "@blocknote/xl-multi-column": "latest", "@y/protocols": "^1.0.6-rc.1", "@y/y": "^14.0.0-rc.23", diff --git a/playground/tsconfig.json b/playground/tsconfig.json index b7da9d8c3d..565f300e5f 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -16,7 +16,10 @@ "jsx": "react-jsx", "composite": true, "types": ["node"], - "rootDir": ".." + "rootDir": "..", + "paths": { + "@shared/*": ["../shared/*"] + } }, "include": ["src", "../examples", "./vite.config.ts"], "references": [ diff --git a/playground/vercel.json b/playground/vercel.json index 3a48e56ba5..3964de463f 100644 --- a/playground/vercel.json +++ b/playground/vercel.json @@ -1,3 +1,7 @@ { + "installCommand": "cd .. && corepack enable && pnpm install", + "buildCommand": "corepack enable && pnpm run build:vercel", + "outputDirectory": "dist", + "ignoreCommand": "[ \"$VERCEL_GIT_COMMIT_REF\" = \"gh-pages\" ]", "rewrites": [{ "source": "/(.*)", "destination": "/" }] } diff --git a/playground/vite.config.ts b/playground/vite.config.ts index dec5f2ee7a..1cd87b5167 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -35,6 +35,12 @@ const devAliases: Record = { __dirname, "../packages/xl-email-exporter/src", ), + "@blocknote/code-block": resolve(__dirname, "../packages/code-block/src"), + "@blocknote/math-block": resolve(__dirname, "../packages/math-block/src"), + "@blocknote/diagram-block": resolve( + __dirname, + "../packages/diagram-block/src", + ), // "@liveblocks/react-blocknote": resolve( // __dirname, // "../../liveblocks/packages/liveblocks-react-blocknote/src/", @@ -61,7 +67,7 @@ export default defineConfig(((conf: { command: string }) => ({ run: { tasks: { build: { - command: "tsgo && vp build", + command: "tsc && vp build", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, @@ -70,12 +76,24 @@ export default defineConfig(((conf: { command: string }) => ({ }, }, }, - plugins: [react(), webpackStats(), Inspect(), tailwindcss()], + plugins: [ + react(), + // The stats are only consumed by RelativeCI, which uploads them from the + // GitHub Actions build. Serializing the (huge) module graph at the end of + // the build costs a lot of memory, which the Vercel build container can't + // spare - it fails spawning processes (EAGAIN) right at that point. + ...(process.env.VERCEL ? [] : [webpackStats()]), + Inspect(), + tailwindcss(), + ], optimizeDeps: { // link: ['vite-react-ts-components'], }, build: { - sourcemap: true, + // Skipped on Vercel for the same reason as `webpackStats` above: emitting + // a map for each of the ~340 chunks doubles the file writes and memory of + // the largest build in the workspace, which is where the container dies. + sourcemap: !process.env.VERCEL, }, preview: { port: 3000, @@ -87,6 +105,33 @@ export default defineConfig(((conf: { command: string }) => ({ allowedHosts: ["host.docker.internal"], }, resolve: { - alias: conf.command === "build" ? undefined : devAliases, + alias: + conf.command === "build" + ? { + // The exporters' optional peer dependencies, used by their + // subpath entries (`…/diagram-block`, `…/math-block`). They + // can't be resolved from the workspace-linked exporter packages + // when those packages' devDependencies aren't installed (e.g. + // Vercel's filtered install), making Vite substitute an empty + // `__vite-optional-peer-dep` stub that fails the build - so + // resolve them from the playground's own dependencies instead. + // Points at `src/` (like the dev aliases): the prefix replace + // bypasses the package's exports map, and only under `src/` do + // subpath imports (`…/diagram-block/docx-exporter`) land on + // real directories with index files. + "@blocknote/diagram-block": resolve( + __dirname, + "../packages/diagram-block/src", + ), + // The shared test-utils package the suggestion-gallery example + // imports; dev mode resolves it via devAliases above. + "@shared": resolve(__dirname, "../shared"), + "@react-pdf/math": resolve( + __dirname, + "node_modules/@react-pdf/math", + ), + katex: resolve(__dirname, "node_modules/katex"), + } + : devAliases, }, })) as Parameters[0]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f902ae81a..6decb347ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,29 +7,35 @@ settings: catalogs: default: vite-plus: - specifier: ^0.1.24 - version: 0.1.24 + specifier: ^0.2.9 + version: 0.2.9 overrides: '@headlessui/react': ^2.2.4 + shiki: ^4.4.3 + '@shikijs/rehype': ^4.4.3 + '@shikijs/types': ^4.4.3 '@tiptap/core': ^3.29.2 '@tiptap/pm': ^3.29.2 - vitest: 4.1.7 - '@vitest/runner': 4.1.7 + '@types/node': ^25.6.0 + jsdom: ^29.0.2 + vitest: 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/mocker': 4.1.10 '@y/y': 14.0.0-rc.23 '@y/prosemirror': 2.0.0-6 lib0: 1.0.0-rc.22 +packageExtensionsChecksum: sha256-RBsr8H6XmGjVk3a5IXktWPY+vN2mX4m0Q/uTlfMsVxo= + patchedDependencies: '@y/prosemirror@2.0.0-6': e49b17b47e301dd138d7e383a779a0e2125bf7f038e10e3c740e26b43d988776 + katex@0.16.47: cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7 importers: .: devDependencies: - '@typescript/native-preview': - specifier: 7.0.0-dev.20260615.1 - version: 7.0.0-dev.20260615.1 bumpp: specifier: ^11.1.0 version: 11.1.0 @@ -46,17 +52,17 @@ importers: specifier: ^10.5.0 version: 10.5.0 oxlint-tsgolint: - specifier: ^0.23.0 - version: 0.23.0 + specifier: ^7.0.2001 + version: 7.0.2001 serve: specifier: 14.2.6 version: 14.2.6 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) wait-on: specifier: 9.0.5 version: 9.0.5 @@ -74,7 +80,7 @@ importers: version: 3.1022.0 '@base-ui/react': specifier: ^1.1.0 - version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.6.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@blocknote/ariakit': specifier: workspace:* version: link:../packages/ariakit @@ -84,9 +90,15 @@ importers: '@blocknote/core': specifier: workspace:* version: link:../packages/core + '@blocknote/diagram-block': + specifier: workspace:* + version: link:../packages/diagram-block '@blocknote/mantine': specifier: workspace:* version: link:../packages/mantine + '@blocknote/math-block': + specifier: workspace:* + version: link:../packages/math-block '@blocknote/react': specifier: workspace:* version: link:../packages/react @@ -119,7 +131,7 @@ importers: version: 0.27.19(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@fumadocs/base-ui': specifier: 16.5.0 - version: 16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2) + version: 16.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2) '@liveblocks/client': specifier: ^3.19.5 version: 3.19.5(@types/json-schema@7.0.15) @@ -155,7 +167,7 @@ importers: version: 3.1.18 '@polar-sh/better-auth': specifier: ^1.6.4 - version: 1.8.3(@polar-sh/sdk@0.42.5)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@stripe/stripe-js@7.9.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(better-auth@1.4.22(better-sqlite3@12.8.0)(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))))(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5)(redux@5.0.1)(zod@4.3.6) + version: 1.8.3(@polar-sh/sdk@0.42.5)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@stripe/stripe-js@7.9.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(better-auth@1.4.22(better-sqlite3@12.8.0)(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.10))(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5)(redux@5.0.1)(zod@4.3.6) '@polar-sh/sdk': specifier: ^0.42.2 version: 0.42.5 @@ -165,27 +177,30 @@ importers: '@react-email/render': specifier: ^2.0.4 version: 2.1.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@react-pdf/math': + specifier: ^2.0.1 + version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5) '@react-pdf/renderer': - specifier: ^4.3.0 - version: 4.3.2(react@19.2.5) + specifier: ^4.5.1 + version: 4.5.1(react@19.2.5) '@sentry/nextjs': specifier: ^10.34.0 - version: 10.47.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(webpack@5.105.4(esbuild@0.27.5)) + version: 10.47.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(webpack@5.105.4(esbuild@0.27.5)) '@shikijs/core': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/engine-javascript': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/langs-precompiled': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/themes': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/types': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@tiptap/core': specifier: ^3.29.2 version: 3.29.2(@tiptap/pm@3.29.2) @@ -224,7 +239,7 @@ importers: version: 3.6.8(@uppy/core@3.13.1) '@vercel/analytics': specifier: ^1.6.1 - version: 1.6.1(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + version: 1.6.1(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) '@y-sweet/react': specifier: ^0.6.3 version: 0.6.4(react@19.2.5)(yjs@13.6.30) @@ -245,43 +260,55 @@ importers: version: 6.0.5(zod@4.3.6) better-auth: specifier: ~1.4.15 - version: 1.4.22(better-sqlite3@12.8.0)(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))) + version: 1.4.22(better-sqlite3@12.8.0)(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.10) better-sqlite3: specifier: ^12.6.2 version: 12.8.0 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 + docx: + specifier: ^9.6.1 + version: 9.6.1 framer-motion: specifier: ^12.26.2 version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) fumadocs-core: specifier: 16.5.0 - version: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + version: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) fumadocs-mdx: specifier: ^14.2.6 - version: 14.2.11(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + version: 14.2.11(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) fumadocs-twoslash: specifier: ^3.1.12 - version: 3.1.15(@fumadocs/base-ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3) + version: 3.1.15(@fumadocs/base-ui@16.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@7.0.2) fumadocs-typescript: specifier: ^5.1.1 - version: 5.2.1(7b71d35b2307cf0dedaa2cbc5003f54e) + version: 5.2.1(c2cf99cf8c13a0f5fb978e1c3d570ec2) fumadocs-ui: specifier: npm:@fumadocs/base-ui@16.5.0 - version: '@fumadocs/base-ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)' + version: '@fumadocs/base-ui@16.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)' + katex: + specifier: ^0.16.11 + version: 0.16.47(patch_hash=cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7) lib0: specifier: 1.0.0-rc.22 version: 1.0.0-rc.22 lucide-react: specifier: ^0.562.0 version: 0.562.0(react@19.2.5) + mathjax-full: + specifier: ^3.2.2 + version: 3.2.2 + mermaid: + specifier: ^11.0.0 + version: 11.16.0 motion: specifier: ^12.28.1 version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) next: - specifier: ^16.2.7 - version: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + specifier: ^16.3.0 + version: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -313,11 +340,14 @@ importers: specifier: ^3.1.0 version: 3.1.0 shiki: - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 tailwind-merge: specifier: ^3.4.0 version: 3.5.0 + typescript-5: + specifier: npm:typescript@^5.9.3 + version: typescript@5.9.3 y-partykit: specifier: ^0.0.25 version: 0.0.25 @@ -341,7 +371,7 @@ importers: specifier: ^2.0.13 version: 2.0.13 '@types/node': - specifier: ^25.0.5 + specifier: ^25.6.0 version: 25.6.0 '@types/nodemailer': specifier: ^7.0.5 @@ -374,8 +404,8 @@ importers: specifier: ^1.4.0 version: 1.4.0 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 examples/01-basic/01-minimal: dependencies: @@ -2546,20 +2576,20 @@ importers: specifier: ^9.0.2 version: 9.1.1(react@19.2.5) '@shikijs/core': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/engine-javascript': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/langs-precompiled': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/themes': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/types': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 react: specifier: ^19.2.3 version: 19.2.5 @@ -2760,9 +2790,15 @@ importers: '@blocknote/core': specifier: latest version: link:../../../packages/core + '@blocknote/diagram-block': + specifier: latest + version: link:../../../packages/diagram-block '@blocknote/mantine': specifier: latest version: link:../../../packages/mantine + '@blocknote/math-block': + specifier: latest + version: link:../../../packages/math-block '@blocknote/react': specifier: latest version: link:../../../packages/react @@ -2781,9 +2817,15 @@ importers: '@mantine/hooks': specifier: ^9.0.2 version: 9.1.1(react@19.2.5) + '@react-pdf/math': + specifier: ^2.0.1 + version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5) '@react-pdf/renderer': - specifier: ^4.3.0 - version: 4.3.2(react@19.2.5) + specifier: ^4.5.1 + version: 4.5.1(react@19.2.5) + mathjax-full: + specifier: ^3.2.2 + version: 3.2.2 react: specifier: ^19.2.3 version: 19.2.5 @@ -2812,9 +2854,15 @@ importers: '@blocknote/core': specifier: latest version: link:../../../packages/core + '@blocknote/diagram-block': + specifier: latest + version: link:../../../packages/diagram-block '@blocknote/mantine': specifier: latest version: link:../../../packages/mantine + '@blocknote/math-block': + specifier: latest + version: link:../../../packages/math-block '@blocknote/react': specifier: latest version: link:../../../packages/react @@ -2833,6 +2881,9 @@ importers: '@mantine/hooks': specifier: ^9.0.2 version: 9.1.1(react@19.2.5) + katex: + specifier: ^0.16.11 + version: 0.16.47(patch_hash=cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7) react: specifier: ^19.2.3 version: 19.2.5 @@ -2861,9 +2912,15 @@ importers: '@blocknote/core': specifier: latest version: link:../../../packages/core + '@blocknote/diagram-block': + specifier: latest + version: link:../../../packages/diagram-block '@blocknote/mantine': specifier: latest version: link:../../../packages/mantine + '@blocknote/math-block': + specifier: latest + version: link:../../../packages/math-block '@blocknote/react': specifier: latest version: link:../../../packages/react @@ -2882,6 +2939,9 @@ importers: '@mantine/hooks': specifier: ^9.0.2 version: 9.1.1(react@19.2.5) + katex: + specifier: ^0.16.11 + version: 0.16.47(patch_hash=cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7) react: specifier: ^19.2.3 version: 19.2.5 @@ -2910,9 +2970,15 @@ importers: '@blocknote/core': specifier: latest version: link:../../../packages/core + '@blocknote/diagram-block': + specifier: latest + version: link:../../../packages/diagram-block '@blocknote/mantine': specifier: latest version: link:../../../packages/mantine + '@blocknote/math-block': + specifier: latest + version: link:../../../packages/math-block '@blocknote/react': specifier: latest version: link:../../../packages/react @@ -3393,6 +3459,156 @@ importers: specifier: ^8.0.0 version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + examples/06-custom-schema/09-math-block: + dependencies: + '@blocknote/ariakit': + specifier: latest + version: link:../../../packages/ariakit + '@blocknote/code-block': + specifier: latest + version: link:../../../packages/code-block + '@blocknote/core': + specifier: latest + version: link:../../../packages/core + '@blocknote/mantine': + specifier: latest + version: link:../../../packages/mantine + '@blocknote/math-block': + specifier: latest + version: link:../../../packages/math-block + '@blocknote/react': + specifier: latest + version: link:../../../packages/react + '@blocknote/shadcn': + specifier: latest + version: link:../../../packages/shadcn + '@mantine/core': + specifier: ^9.0.2 + version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mantine/hooks': + specifier: ^9.0.2 + version: 9.1.1(react@19.2.5) + react: + specifier: ^19.2.3 + version: 19.2.5 + react-dom: + specifier: ^19.2.3 + version: 19.2.5(react@19.2.5) + react-icons: + specifier: ^5.5.0 + version: 5.6.0(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.3 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + vite: + specifier: ^8.0.0 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + + examples/06-custom-schema/10-diagram-block: + dependencies: + '@blocknote/ariakit': + specifier: latest + version: link:../../../packages/ariakit + '@blocknote/code-block': + specifier: latest + version: link:../../../packages/code-block + '@blocknote/core': + specifier: latest + version: link:../../../packages/core + '@blocknote/diagram-block': + specifier: latest + version: link:../../../packages/diagram-block + '@blocknote/mantine': + specifier: latest + version: link:../../../packages/mantine + '@blocknote/react': + specifier: latest + version: link:../../../packages/react + '@blocknote/shadcn': + specifier: latest + version: link:../../../packages/shadcn + '@mantine/core': + specifier: ^9.0.2 + version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mantine/hooks': + specifier: ^9.0.2 + version: 9.1.1(react@19.2.5) + react: + specifier: ^19.2.3 + version: 19.2.5 + react-dom: + specifier: ^19.2.3 + version: 19.2.5(react@19.2.5) + react-icons: + specifier: ^5.5.0 + version: 5.6.0(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.3 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + vite: + specifier: ^8.0.0 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + + examples/06-custom-schema/11-source-with-preview: + dependencies: + '@blocknote/ariakit': + specifier: latest + version: link:../../../packages/ariakit + '@blocknote/core': + specifier: latest + version: link:../../../packages/core + '@blocknote/mantine': + specifier: latest + version: link:../../../packages/mantine + '@blocknote/react': + specifier: latest + version: link:../../../packages/react + '@blocknote/shadcn': + specifier: latest + version: link:../../../packages/shadcn + '@mantine/core': + specifier: ^9.0.2 + version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mantine/hooks': + specifier: ^9.0.2 + version: 9.1.1(react@19.2.5) + react: + specifier: ^19.2.3 + version: 19.2.5 + react-dom: + specifier: ^19.2.3 + version: 19.2.5(react@19.2.5) + react-icons: + specifier: ^5.5.0 + version: 5.6.0(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.3 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + vite: + specifier: ^8.0.0 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + examples/06-custom-schema/draggable-inline-content: dependencies: '@blocknote/ariakit': @@ -4240,9 +4456,6 @@ importers: '@blocknote/shadcn': specifier: latest version: link:../../../packages/shadcn - '@blocknote/shared': - specifier: latest - version: link:../../../shared '@blocknote/xl-multi-column': specifier: latest version: link:../../../packages/xl-multi-column @@ -4940,14 +5153,14 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plugin-externalize-deps: specifier: ^0.10.0 version: 0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) packages/code-block: dependencies: @@ -4955,17 +5168,17 @@ importers: specifier: workspace:^ version: link:../core '@shikijs/core': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/engine-javascript': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/langs-precompiled': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@shikijs/themes': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 devDependencies: rimraf: specifier: ^5.0.10 @@ -4974,15 +5187,15 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) optionalDependencies: '@shikijs/types': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 packages/core: dependencies: @@ -4993,8 +5206,8 @@ importers: specifier: ^0.1.4 version: 0.1.4(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-view@1.42.2) '@shikijs/types': - specifier: ^4 - version: 4.0.2 + specifier: ^4.4.3 + version: 4.4.3 '@tiptap/core': specifier: ^3.29.2 version: 3.29.2(@tiptap/pm@3.29.2) @@ -5041,8 +5254,8 @@ importers: specifier: 1.0.0-rc.22 version: 1.0.0-rc.22 prosemirror-highlight: - specifier: ^0.15.1 - version: 0.15.1(@shikijs/types@4.0.2)(@types/hast@3.0.4)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + specifier: ^0.15.3 + version: 0.15.3(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) prosemirror-model: specifier: ^1.25.11 version: 1.25.11 @@ -5069,11 +5282,11 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) y-prosemirror: specifier: ^1.3.7 version: 1.3.7(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-view@1.42.2)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30) @@ -5087,8 +5300,8 @@ importers: packages/dev-scripts: devDependencies: '@types/node': - specifier: ^22.0.0 - version: 22.13.13 + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^19.2.3 version: 19.2.14 @@ -5111,13 +5324,13 @@ importers: specifier: ^4.20.6 version: 4.21.0 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - packages/mantine: + packages/diagram-block: dependencies: '@blocknote/core': specifier: workspace:^ @@ -5125,28 +5338,52 @@ importers: '@blocknote/react': specifier: workspace:^ version: link:../react - '@mantine/core': - specifier: ^8.3.11 || ^9.0.2 - version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@mantine/hooks': - specifier: ^8.3.11 || ^9.0.2 - version: 9.1.1(react@19.2.5) + mermaid: + specifier: ^11.0.0 + version: 11.16.0 devDependencies: + '@blocknote/shared': + specifier: workspace:^ + version: link:../../shared + '@blocknote/xl-docx-exporter': + specifier: workspace:^ + version: link:../xl-docx-exporter + '@blocknote/xl-email-exporter': + specifier: workspace:^ + version: link:../xl-email-exporter + '@blocknote/xl-odt-exporter': + specifier: workspace:^ + version: link:../xl-odt-exporter + '@blocknote/xl-pdf-exporter': + specifier: workspace:^ + version: link:../xl-pdf-exporter + '@react-email/components': + specifier: ^1.0.12 + version: 1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@react-pdf/renderer': + specifier: ^4.5.1 + version: 4.5.1(react@19.2.5) '@types/react': specifier: ^19.2.3 version: 19.2.14 '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) - '@vitejs/plugin-react': - specifier: ^6.0.1 - version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + '@zip.js/zip.js': + specifier: ^2.8.8 + version: 2.8.26 + docx: + specifier: ^9.6.1 + version: 9.6.1 react: specifier: ^19.2.5 version: 19.2.5 react-dom: specifier: ^19.2.5 version: 19.2.5(react@19.2.5) + react-element-to-jsx-string: + specifier: ^17.0.1 + version: 17.0.1(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5) react-icons: specifier: ^5.5.0 version: 5.6.0(react@19.2.5) @@ -5157,51 +5394,27 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 - vite-plugin-externalize-deps: - specifier: ^0.10.0 - version: 0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - packages/react: + packages/mantine: dependencies: '@blocknote/core': specifier: workspace:^ version: link:../core - '@emoji-mart/data': - specifier: ^1.2.1 - version: 1.2.1 - '@floating-ui/react': - specifier: ^0.27.18 - version: 0.27.19(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@tiptap/core': - specifier: ^3.29.2 - version: 3.29.2(@tiptap/pm@3.29.2) - '@tiptap/pm': - specifier: ^3.29.2 - version: 3.29.2 - '@tiptap/react': - specifier: ^3.29.2 - version: 3.29.2(@floating-ui/dom@1.7.6)(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - emoji-mart: - specifier: ^5.6.0 - version: 5.6.0 - fast-deep-equal: - specifier: ^3.1.3 - version: 3.1.3 - use-sync-external-store: - specifier: 1.6.0 - version: 1.6.0(react@19.2.5) + '@blocknote/react': + specifier: workspace:^ + version: link:../react + '@mantine/core': + specifier: ^8.3.11 || ^9.0.2 + version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mantine/hooks': + specifier: ^8.3.11 || ^9.0.2 + version: 9.1.1(react@19.2.5) devDependencies: - '@types/lodash.foreach': - specifier: ^4.5.9 - version: 4.5.9 - '@types/lodash.groupby': - specifier: ^4.6.9 - version: 4.6.9 '@types/react': specifier: ^19.2.3 version: 19.2.14 @@ -5227,20 +5440,16 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plugin-externalize-deps: specifier: ^0.10.0 version: 0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - optionalDependencies: - '@types/use-sync-external-store': - specifier: 1.5.0 - version: 1.5.0 + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - packages/server-util: + packages/math-block: dependencies: '@blocknote/core': specifier: workspace:^ @@ -5248,19 +5457,187 @@ importers: '@blocknote/react': specifier: workspace:^ version: link:../react - '@tiptap/pm': - specifier: ^3.29.2 - version: 3.29.2 - jsdom: - specifier: ^25.0.1 - version: 25.0.1(canvas@2.11.2) - yjs: - specifier: ^13.6.27 - version: 13.6.30 - devDependencies: - '@types/jsdom': - specifier: ^21.1.7 - version: 21.1.7 + '@handlewithcare/prosemirror-inputrules': + specifier: ^0.1.4 + version: 0.1.4(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-view@1.42.2) + katex: + specifier: ^0.16.11 + version: 0.16.47(patch_hash=cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7) + prosemirror-model: + specifier: ^1.25.4 + version: 1.25.11 + prosemirror-state: + specifier: ^1.4.4 + version: 1.4.4 + devDependencies: + '@blocknote/shared': + specifier: workspace:^ + version: link:../../shared + '@blocknote/xl-docx-exporter': + specifier: workspace:^ + version: link:../xl-docx-exporter + '@blocknote/xl-email-exporter': + specifier: workspace:^ + version: link:../xl-email-exporter + '@blocknote/xl-odt-exporter': + specifier: workspace:^ + version: link:../xl-odt-exporter + '@blocknote/xl-pdf-exporter': + specifier: workspace:^ + version: link:../xl-pdf-exporter + '@react-email/components': + specifier: ^1.0.12 + version: 1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@react-pdf/math': + specifier: ^2.0.1 + version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5) + '@react-pdf/renderer': + specifier: ^4.5.1 + version: 4.5.1(react@19.2.5) + '@types/katex': + specifier: ^0.16.7 + version: 0.16.8 + '@types/react': + specifier: ^19.2.3 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@zip.js/zip.js': + specifier: ^2.8.8 + version: 2.8.26 + docx: + specifier: ^9.6.1 + version: 9.6.1 + mathjax-full: + specifier: ^3.2.2 + version: 3.2.2 + mathml2omml: + specifier: ^0.5.0 + version: 0.5.0 + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + react-element-to-jsx-string: + specifier: ^17.0.1 + version: 17.0.1(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5) + react-icons: + specifier: ^5.5.0 + version: 5.6.0(react@19.2.5) + rimraf: + specifier: ^5.0.10 + version: 5.0.10 + rollup-plugin-webpack-stats: + specifier: ^0.2.6 + version: 0.2.6(rollup@4.60.1) + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vite-plus: + specifier: 'catalog:' + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + xml-formatter: + specifier: ^3.6.7 + version: 3.7.0 + + packages/react: + dependencies: + '@blocknote/core': + specifier: workspace:^ + version: link:../core + '@emoji-mart/data': + specifier: ^1.2.1 + version: 1.2.1 + '@floating-ui/react': + specifier: ^0.27.18 + version: 0.27.19(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tiptap/core': + specifier: ^3.29.2 + version: 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': + specifier: ^3.29.2 + version: 3.29.2 + '@tiptap/react': + specifier: ^3.29.2 + version: 3.29.2(@floating-ui/dom@1.7.6)(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + emoji-mart: + specifier: ^5.6.0 + version: 5.6.0 + fast-deep-equal: + specifier: ^3.1.3 + version: 3.1.3 + use-sync-external-store: + specifier: 1.6.0 + version: 1.6.0(react@19.2.5) + devDependencies: + '@types/lodash.foreach': + specifier: ^4.5.9 + version: 4.5.9 + '@types/lodash.groupby': + specifier: ^4.6.9 + version: 4.6.9 + '@types/react': + specifier: ^19.2.3 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + react-icons: + specifier: ^5.5.0 + version: 5.6.0(react@19.2.5) + rimraf: + specifier: ^5.0.10 + version: 5.0.10 + rollup-plugin-webpack-stats: + specifier: ^0.2.6 + version: 0.2.6(rollup@4.60.1) + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vite-plugin-externalize-deps: + specifier: ^0.10.0 + version: 0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + vite-plus: + specifier: 'catalog:' + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + optionalDependencies: + '@types/use-sync-external-store': + specifier: 1.5.0 + version: 1.5.0 + + packages/server-util: + dependencies: + '@blocknote/core': + specifier: workspace:^ + version: link:../core + '@blocknote/react': + specifier: workspace:^ + version: link:../react + '@tiptap/pm': + specifier: ^3.29.2 + version: 3.29.2 + jsdom: + specifier: ^29.0.2 + version: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) + yjs: + specifier: ^13.6.27 + version: 13.6.30 + devDependencies: + '@types/jsdom': + specifier: ^21.1.7 + version: 21.1.7 '@types/react': specifier: ^19.2.3 version: 19.2.14 @@ -5280,11 +5657,11 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) y-prosemirror: specifier: ^1.3.7 version: 1.3.7(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-view@1.42.2)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30) @@ -5320,8 +5697,8 @@ importers: version: 4.2.2 devDependencies: '@types/node': - specifier: ^20.19.22 - version: 20.19.39 + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^19.2.3 version: 19.2.14 @@ -5344,14 +5721,14 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plugin-externalize-deps: specifier: ^0.10.0 version: 0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) packages/xl-ai: dependencies: @@ -5428,9 +5805,6 @@ importers: '@ai-sdk/openai-compatible': specifier: ^2.0.2 version: 2.0.2(zod@4.3.6) - '@blocknote/shared': - specifier: workspace:^ - version: link:../../shared '@blocknote/xl-multi-column': specifier: workspace:^ version: link:../xl-multi-column @@ -5453,8 +5827,8 @@ importers: specifier: ^4.6.9 version: 4.6.9 '@types/node': - specifier: 22.13.13 - version: 22.13.13 + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^19.2.3 version: 19.2.14 @@ -5472,10 +5846,10 @@ importers: version: 4.0.3 msw: specifier: ^2.11.5 - version: 2.11.5(@types/node@22.13.13)(typescript@5.9.3) + version: 2.11.5(@types/node@25.6.0)(typescript@7.0.2) msw-snapshot: specifier: ^5.3.0 - version: 5.3.0(msw@2.11.5(@types/node@22.13.13)(typescript@5.9.3)) + version: 5.3.0(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2)) react-icons: specifier: ^5.5.0 version: 5.6.0(react@19.2.5) @@ -5486,8 +5860,8 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 undici: specifier: ^6.22.0 version: 6.25.0 @@ -5496,7 +5870,7 @@ importers: version: 0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) yjs: specifier: ^13.6.27 version: 13.6.30 @@ -5535,8 +5909,8 @@ importers: version: 4.12.14 devDependencies: '@types/node': - specifier: 22.13.13 - version: 22.13.13 + specifier: ^25.6.0 + version: 25.6.0 rimraf: specifier: ^5.0.10 version: 5.0.10 @@ -5544,20 +5918,20 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 undici: specifier: ^6.22.0 version: 6.25.0 vite-node: specifier: ^6.0.0 - version: 6.0.0(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + version: 6.0.0(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) vite-plugin-externalize-deps: specifier: ^0.10.0 - version: 0.10.0(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + version: 0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) packages/xl-docx-exporter: dependencies: @@ -5602,11 +5976,11 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) xml-formatter: specifier: ^3.6.7 version: 3.7.0 @@ -5654,11 +6028,11 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) packages/xl-multi-column: dependencies: @@ -5691,8 +6065,8 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) jsdom: - specifier: ^25.0.1 - version: 25.0.1(canvas@2.11.2) + specifier: ^29.0.2 + version: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) react: specifier: ^19.2.5 version: 19.2.5 @@ -5709,11 +6083,11 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) packages/xl-odt-exporter: dependencies: @@ -5737,8 +6111,8 @@ importers: specifier: ^16.3.0 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/node': - specifier: 22.13.13 - version: 22.13.13 + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^19.2.3 version: 19.2.14 @@ -5758,11 +6132,11 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) xml-formatter: specifier: ^3.6.7 version: 3.7.0 @@ -5776,8 +6150,8 @@ importers: specifier: workspace:^ version: link:../xl-multi-column '@react-pdf/renderer': - specifier: ^4.3.0 - version: 4.3.2(react@19.2.5) + specifier: ^4.5.1 + version: 4.5.1(react@19.2.5) devDependencies: '@blocknote/shared': specifier: workspace:^ @@ -5785,24 +6159,12 @@ importers: '@testing-library/react': specifier: ^16.3.0 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@types/jest-image-snapshot': - specifier: ^6.4.0 - version: 6.4.1 - '@types/jsdom': - specifier: ^21.1.7 - version: 21.1.7 '@types/react': specifier: ^19.2.3 version: 19.2.14 '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) - jest-image-snapshot: - specifier: ^6.5.1 - version: 6.5.2 - pdf-to-img: - specifier: ^4.5.0 - version: 4.5.0 react: specifier: ^19.2.5 version: 19.2.5 @@ -5819,11 +6181,11 @@ importers: specifier: ^0.2.6 version: 0.2.6(rollup@4.60.1) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) playground: dependencies: @@ -5845,9 +6207,15 @@ importers: '@blocknote/core': specifier: workspace:^ version: link:../packages/core + '@blocknote/diagram-block': + specifier: workspace:^ + version: link:../packages/diagram-block '@blocknote/mantine': specifier: workspace:^ version: link:../packages/mantine + '@blocknote/math-block': + specifier: workspace:^ + version: link:../packages/math-block '@blocknote/react': specifier: workspace:^ version: link:../packages/react @@ -5908,6 +6276,9 @@ importers: '@mui/material': specifier: ^5.18.0 version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@react-pdf/math': + specifier: ^2.0.1 + version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5) '@uppy/core': specifier: ^3.13.1 version: 3.13.1 @@ -5953,6 +6324,9 @@ importers: docx: specifier: ^9.5.1 version: 9.6.1 + katex: + specifier: ^0.16.11 + version: 0.16.47(patch_hash=cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7) react: specifier: ^19.2.5 version: 19.2.5 @@ -5971,10 +6345,10 @@ importers: devDependencies: '@tailwindcss/vite': specifier: ^4.1.14 - version: 4.2.2(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.2.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) '@types/node': - specifier: 22.13.13 - version: 22.13.13 + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^19.2.3 version: 19.2.14 @@ -5983,7 +6357,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) rimraf: specifier: ^5.0.10 version: 5.0.10 @@ -5998,10 +6372,10 @@ importers: version: 1.4.0 vite-plugin-inspect: specifier: 12.0.0-beta.1 - version: 12.0.0-beta.1(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0) + version: 12.0.0-beta.1(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0) vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) shared: dependencies: @@ -6013,14 +6387,20 @@ importers: version: 0.2.2 devDependencies: '@types/node': - specifier: 22.13.13 - version: 22.13.13 + specifier: ^25.6.0 + version: 25.6.0 + '@zip.js/zip.js': + specifier: ^2.8.8 + version: 2.8.26 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^7.0.2 + version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + xml-formatter: + specifier: ^3.6.7 + version: 3.7.0 tests: dependencies: @@ -6034,39 +6414,57 @@ importers: '@blocknote/core': specifier: workspace:^ version: link:../packages/core + '@blocknote/diagram-block': + specifier: workspace:^ + version: link:../packages/diagram-block '@blocknote/mantine': specifier: workspace:^ version: link:../packages/mantine + '@blocknote/math-block': + specifier: workspace:^ + version: link:../packages/math-block '@blocknote/react': specifier: workspace:^ version: link:../packages/react '@blocknote/shadcn': specifier: workspace:^ version: link:../packages/shadcn + '@blocknote/xl-email-exporter': + specifier: workspace:^ + version: link:../packages/xl-email-exporter '@blocknote/xl-multi-column': specifier: workspace:^ version: link:../packages/xl-multi-column + '@blocknote/xl-pdf-exporter': + specifier: workspace:^ + version: link:../packages/xl-pdf-exporter '@playwright/test': specifier: 1.60.0 version: 1.60.0 + '@react-pdf/renderer': + specifier: ^4.5.1 + version: 4.5.1(react@19.2.5) '@tailwindcss/vite': specifier: ^4.1.14 - version: 4.2.2(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.2.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) '@tiptap/pm': specifier: ^3.29.2 version: 3.29.2 '@types/node': - specifier: ^20.19.22 - version: 20.19.39 + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^19.2.3 version: 19.2.14 '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) + '@vitest/browser-playwright': + specifier: 4.1.10 + version: 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(playwright@1.60.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) '@vitest/ui': specifier: 4.1.5 - version: 4.1.5(vitest@4.1.7) + version: 4.1.5(vitest@4.1.10) '@y/protocols': specifier: ^1.0.6-rc.1 version: 1.0.6-rc.1(@y/y@14.0.0-rc.23) @@ -6076,6 +6474,9 @@ importers: htmlfy: specifier: ^0.6.7 version: 0.6.7 + pdfjs-dist: + specifier: ^4.10.38 + version: 4.10.38 react: specifier: ^19.2.5 version: 19.2.5 @@ -6090,10 +6491,10 @@ importers: version: 5.0.10 vite-plus: specifier: 'catalog:' - version: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + version: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) vitest-browser-react: specifier: ^2.2.0 - version: 2.2.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.7) + version: 2.2.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.10) packages: @@ -6159,6 +6560,9 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@ariakit/core@0.4.18': resolution: {integrity: sha512-9urEa+GbZTSyredq3B/3thQjTcSZSUC68XctwCkJNH/xNfKN5O+VThiem2rcJxpsGw8sRUQenhagZi0yB4foyg==} @@ -6174,9 +6578,6 @@ packages: react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@asamuzakjp/css-color@3.2.0': - resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - '@asamuzakjp/css-color@5.1.11': resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -6439,17 +6840,6 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.3.0': - resolution: {integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - '@base-ui/react@1.6.0': resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==} engines: {node: '>=14.0.0'} @@ -6467,16 +6857,6 @@ packages: date-fns: optional: true - '@base-ui/utils@0.2.6': - resolution: {integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw==} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - '@base-ui/utils@0.3.1': resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==} peerDependencies: @@ -6514,25 +6894,23 @@ packages: '@better-fetch/fetch@1.1.21': resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@blazediff/core@1.9.1': + resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} - '@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-calc@3.2.0': resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} engines: {node: '>=20.19.0'} @@ -6540,13 +6918,6 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@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-color-parser@4.1.0': resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} engines: {node: '>=20.19.0'} @@ -6554,12 +6925,6 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@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-parser-algorithms@4.0.0': resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} engines: {node: '>=20.19.0'} @@ -6574,10 +6939,6 @@ packages: css-tree: optional: true - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} - '@csstools/css-tokenizer@4.0.0': resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} @@ -6588,8 +6949,8 @@ packages: '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} '@emnapi/runtime@1.9.2': resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} @@ -7140,156 +7501,171 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.4': + resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==} + '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-arm64@0.35.3': + resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-x64@0.35.3': + resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-freebsd-wasm32@0.35.3': + resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.2': + resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.3.2': + resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.3.2': + resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.3.2': + resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + '@img/sharp-libvips-linux-ppc64@1.3.2': + resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + '@img/sharp-libvips-linux-riscv64@1.3.2': + resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.3.2': + resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.3.2': + resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.3.2': + resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm64@0.35.3': + resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm@0.35.3': + resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} + engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-ppc64@0.35.3': + resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} + engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-riscv64@0.35.3': + resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} + engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-s390x@0.35.3': + resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} + engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-x64@0.35.3': + resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-arm64@0.35.3': + resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-x64@0.35.3': + resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-wasm32@0.35.3': + resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.3': + resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} + engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-arm64@0.35.3': + resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-ia32@0.35.3': + resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} + engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-x64@0.35.3': + resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -7301,7 +7677,7 @@ packages: resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} engines: {node: '>=18'} peerDependencies: - '@types/node': '>=18' + '@types/node': ^25.6.0 peerDependenciesMeta: '@types/node': optional: true @@ -7310,7 +7686,7 @@ packages: resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} engines: {node: '>=18'} peerDependencies: - '@types/node': '>=18' + '@types/node': ^25.6.0 peerDependenciesMeta: '@types/node': optional: true @@ -7323,7 +7699,7 @@ packages: resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} engines: {node: '>=18'} peerDependencies: - '@types/node': '>=18' + '@types/node': ^25.6.0 peerDependenciesMeta: '@types/node': optional: true @@ -7332,30 +7708,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jest/diff-sequences@30.3.0': - resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/expect-utils@30.3.0': - resolution: {integrity: sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/get-type@30.1.0': - resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/pattern@30.0.1': - resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/schemas@30.0.5': - resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/types@30.3.0': - resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -7462,10 +7814,6 @@ packages: peerDependencies: react: ^19.2.0 - '@mapbox/node-pre-gyp@1.0.11': - resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} - hasBin: true - '@marsidev/react-turnstile@1.5.0': resolution: {integrity: sha512-Ph6mcj8u9WBDsBO7s9jKPsyRDz1sBPBJwrk+Ngx09vFInvKsQ6U6kW5amEcGq4dHOreB6DgFrOJk7/fy318YlQ==} peerDependencies: @@ -7475,6 +7823,9 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} + '@mswjs/interceptors@0.37.6': resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} engines: {node: '>=18'} @@ -7577,61 +7928,65 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@16.2.7': - resolution: {integrity: sha512-tMJizPlj6ZYpBMMdK8S0LJufrP4QTdR6pcv9KQ/bVETPAmg0j1mlHE9G2c38UyGHxoBapgwuj7XjbGJ2RcDFOg==} + '@next/env@16.3.0': + resolution: {integrity: sha512-o9r1S0BNiNreHP9Vs+Qnqd9kviDkJh8xIACY7UFZSmiGbbQRzPBBosvHzAU4TULHOIuOj/18RSsyz2qrREmIFw==} - '@next/swc-darwin-arm64@16.2.7': - resolution: {integrity: sha512-vm1EDI/pVaBNNiychmxk3fft+OhQPVD9cIM/tReLZIQ3TfQ4kqI9DwKk00dzuS1ulC7icbrzCFrmRRlk9PfNdw==} + '@next/swc-darwin-arm64@16.3.0': + resolution: {integrity: sha512-55hpqq18bEVAlxedlTt3tFqZmKg2nUXT1kn1G/BGEy0R13h3LwtwHPVzzjG6P4LLeOHE32PFDQUVaJEWvBEZBw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.7': - resolution: {integrity: sha512-O3IRSv1ZBL1zs0WrIgefTEcTKFVn+ryxBNe54erJ6KsD+2f/Mmt7g2jOYh8PSBdUwPtKQJuCsTMlZ7tIu2AcsQ==} + '@next/swc-darwin-x64@16.3.0': + resolution: {integrity: sha512-SOi96kSaF5T+0wW4koiM1bWzSPwjzTesC1p3df+FjdOi5LIQkBK/blxh7HdoKnNuI4PURF1OO7TZqtfnbWDSgw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.7': - resolution: {integrity: sha512-Re6PZtjBDd0aMU+VcZcC/PrIvj4WhrjDYtMhhCVQamWN4L90EVP0pcEOBQD25prSlw7OzNw5QpHLWMilRLsRNw==} + '@next/swc-linux-arm64-gnu@16.3.0': + resolution: {integrity: sha512-P0gZAoPMF4dyTRzhmkV4PrqVzSOB6t4mC1oI3c4dqijJ+OVEVx5clIXAKR4/uQpsqw2KKM/0D5tVumcR2r5blg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.7': - resolution: {integrity: sha512-qyogG9QtBzWxgJfeGBvOEHI3851gTfCF3wLZ5RDLTBJGAmE9p1qDwKCOdrBrvBzRvYDT+gUDp72pzlSEfAXgNA==} + '@next/swc-linux-arm64-musl@16.3.0': + resolution: {integrity: sha512-tXXGKJw0m37O0eKJARVTX/TheKPhz0QFVtVVZXmOig+9YKLQOSP6hvf2pxv5DO7CLEJyTHx3Pg043CDQkv1G4Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.7': - resolution: {integrity: sha512-Vhe4ZDuBpmMogrGi5D4R2Kq4JAQlj6+wvgaFYy31zfES0zPmt6TLA+cuYpM/OLrPZjo2MYQTHVqNUSCR6+fDZQ==} + '@next/swc-linux-x64-gnu@16.3.0': + resolution: {integrity: sha512-pjGxK5EY7yWml78ALejFkWmgHsU7wbFQrISiugpH6FbUJhgEvw3xFZ/EBAtLl7QtL0WdQKiG9eWJ3mOKGTukHw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.7': - resolution: {integrity: sha512-srvian89JahFLw1YLBEuhvPJ0DO5lpUeJQMXy4xYo7g628ZlNgXdNkqoxSAv9OYrBfByh6vxISMwW/mRbzCY+g==} + '@next/swc-linux-x64-musl@16.3.0': + resolution: {integrity: sha512-sjo++Xx+lomlPs3HRsHWhVDyGG6ms1kGW5EtHLERdII8AyG1i+f6aq68xHREO6AEMlhjTNEWBSmfJfqm9orf7g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.7': - resolution: {integrity: sha512-GX3wvLpULFuRFJzwHaKfm7QZJ18F4ZSuxlPJ96BoBglCzBmdSjyeBKF+ZhWhvL/ckxNfLnNa7bsObO2ipYpszw==} + '@next/swc-win32-arm64-msvc@16.3.0': + resolution: {integrity: sha512-C5JSgiO54wURdaxdEUIXqkz04uMqC9UmPX1gtDrV/5Tf1UowdWYI8uA5hfFbPolTlp0q4KZ60xlHePNibf0VIw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.7': - resolution: {integrity: sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw==} + '@next/swc-win32-x64-msvc@16.3.0': + resolution: {integrity: sha512-fDOggsweNb5SSw0ZKVk6U+gxSyGFFlIBY/LBc1r8GUj4u/6t6oArL+Pmkg0MBnsgR+KkdsURilVH4F3GXUGepA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + '@noble/ciphers@2.1.1': resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} engines: {node: '>= 20.19.0'} @@ -7865,292 +8220,292 @@ packages: resolution: {integrity: sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA==} engines: {node: '>= 20.0.0'} - '@oxc-project/runtime@0.133.0': - resolution: {integrity: sha512-PkvjA1Lq5++V5S1E6Patr92ZVcieE6EalDr1VJTqv4BnjZdOUC4W3p8k1wMXSd5/2aFP4b/A6N5sg2Bkzcr9vQ==} + '@oxc-project/runtime@0.143.0': + resolution: {integrity: sha512-zIuXUf+YGIgsPk0xlQmzTY8NCSc8jE/pSfDodlQ9H3EGZABmr+AtIjXRrnpQAXuXzhDSNqZz9cuhud8hDDLvpg==} engines: {node: ^20.19.0 || >=22.12.0} '@oxc-project/types@0.124.0': resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} - '@oxc-project/types@0.133.0': - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxc-project/types@0.143.0': + resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} - '@oxfmt/binding-android-arm-eabi@0.52.0': - resolution: {integrity: sha512-17EMSJnQ9g+upVHrAUYDMfH5lvRKQ9Nvg8WtEoH72oDr1VpWz+7/o3tD97U1EToen2YAQ/68JmtDYkQUi20dfQ==} + '@oxfmt/binding-android-arm-eabi@0.62.0': + resolution: {integrity: sha512-pdsv0C4gPjJ8H1+sd8u0BDx+yLACTL+rgeMIOL1ln4ihSnhw8CWXtYWgvcSkyTfgGBIzFKab+d8rx9Xl4en/Kw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.52.0': - resolution: {integrity: sha512-A2G1IdwGEW2lLJkIxcvuirRH1CzSl/e0NX11zTlW1gvxJThfwbI/BEoaKrTNpm7M2FchvIf6guvIQU7d5iz+OQ==} + '@oxfmt/binding-android-arm64@0.62.0': + resolution: {integrity: sha512-WC3YQ7uS/KtDrjmqwBviwFKe9qeoi+eXx8aX1z/ffG23Md75myjrJaQqTuJvdOLPoa4EYTjDWH0dHXfwulCVog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.52.0': - resolution: {integrity: sha512-f9+bLvOYxy7NttCLFTvQ7afmqDOWY4wIP9xdvfj5trQ1qj6f2UFAGwZESlfsMjvJNTyRpXfIlOanCI9FOvoeQA==} + '@oxfmt/binding-darwin-arm64@0.62.0': + resolution: {integrity: sha512-GM8Yf3LjjaR1I8PD0SfeoIlwhsh9GvSF+cQ8sf624Yxnjsyumn95aFzYfKJVefblfDIiOAnZ7QVm2sa21Er/0Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.52.0': - resolution: {integrity: sha512-YSTB9sJ5nnQd/Q0ddHkgof0ZCHPAnWZT1IW2SJ8omz7CP7KluJhO1fNHrpqdxCtpztJwSs4hY1uAee35wKxxaw==} + '@oxfmt/binding-darwin-x64@0.62.0': + resolution: {integrity: sha512-d5THp7F8bCxLqNogEXDORRsQD6dosf3EyFtnXfBer6v+8tGdcWIjoDX9WaXrrF/26zOmL8qHpPTKCEvpBDmZkQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.52.0': - resolution: {integrity: sha512-NIrRNTTPCs4UbmVs0bxLSCDlLCtIRMJIXklNKaXa5Oj2/K1UIMBvgE8+uPVo01Io3N9HF0+GAX+aAHjUgZS7vA==} + '@oxfmt/binding-freebsd-x64@0.62.0': + resolution: {integrity: sha512-1DnrtXGZooOZ0fHgAXZUaDQzBVh1CM2MNW4oBXyQ2aWKvCHjyljvT9fgBkOM0fEOb96X5eqtcfJ0YUVt9jj66g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.52.0': - resolution: {integrity: sha512-JXUCde8mn3GpgQouz2PXUokgy/uT1QrRJBL2s983VWcSQp62wTFYiNXgTKdeo1Jgbr0IgUnKKvzIk/YBlj/nVQ==} + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': + resolution: {integrity: sha512-4pQDHOYRH+Huqe0StIaWyvk2CVl/aTaqSrbZpA3/pLS2xH24ME7lBgYprhQF2fRkHBzhGGGKliwxFsDdHwx59g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.52.0': - resolution: {integrity: sha512-psbUXaRZ+V8DaXz10Qf7LSHtdtdKAmC8fxXgeU608jjzrmWK4quamZMOpl6sf+dikoFHA85uE93Q0BqxrCdQrQ==} + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': + resolution: {integrity: sha512-X0jAaZJFMCVKhB6YyWVTQ/wN2DLsBcZKSMqTS76bF6riT+XZdtg2FPEdjDvdVbunO9cG+tWiVaEs4Zs38lxYog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.52.0': - resolution: {integrity: sha512-Jw7MgWUU9lcLCcy82updISP3EthTlfvAwR6gWNxPzqly7+fLvOi2gHQE9xXQjpqaVLm/8P+gOzlv9ODuoVlaaw==} + '@oxfmt/binding-linux-arm64-gnu@0.62.0': + resolution: {integrity: sha512-682Z8T5s8T5ATArYtsejKvbIfd8LEAXyyDkKkoZVq8HND7Vx8TYLlrDjDSeYfodMeVwHOgkj13lJYR8cj6vUSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.52.0': - resolution: {integrity: sha512-wZg6bLjDvh2KibyI3QFUYo8GTXneIFsd0JvehtvJiUmQ8WRPERgxd/VM4ctWb86U5FT1FkqgS8/wZKVB+AZScg==} + '@oxfmt/binding-linux-arm64-musl@0.62.0': + resolution: {integrity: sha512-lk25fAl7KWaLWVJcW0CHEXB7QlQZtx5eDkjpaGMK0hzXTjUe0Wmlu8IKuFHoviSOcEJedRTs4VE/506VqGxGew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.52.0': - resolution: {integrity: sha512-IngE8uxhNvxcMrLjZNDo9xNLY7rEK33AKnaMd2B46he1e/mz2CfcW6If/U1wUjdRZddm1QzQaciqZkuMkdh1FA==} + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': + resolution: {integrity: sha512-SFyNqHQLwySceWNLhiSldx7wPXRAzP0L0WcW9GegP3uWrpZGJiZlQO85NbHAFPEfxR9PhZ9qSnZryEh7+v+4Gw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.52.0': - resolution: {integrity: sha512-H3+DdFMv/efN3Efmhsv18jDrpiWWqKG7wsfAlQBqAt6z/E2Bx+TwEj2Nowe51CPOWB8/mFBC2dAMSgVFLvvowA==} + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': + resolution: {integrity: sha512-KYj55C1ywJfHo6+aKDuEmUtVEdJALsC5GwayDGsI6FGz2GxFqNr/mA8nxVsNbJzm7sE5MRqTQ9ziImSzhYXysA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.52.0': - resolution: {integrity: sha512-zji+1kb7lJKohSDjzC1IsS+K/cKRs1hdVf0ZH0VbdbiakmtLvN9twBoXo/k8VdjFax7kfo+DyPxS7vv52br1aw==} + '@oxfmt/binding-linux-riscv64-musl@0.62.0': + resolution: {integrity: sha512-BhZDNo5GOU5nC378RhD0/XpvaEBHsH3HLgJp8YZX3A0InC7oivzA63HsRmiXFLtLSHAstEVrDf6fbC7Rs8Jh/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.52.0': - resolution: {integrity: sha512-hcLBYedpCy7ToUvvBidWk7+11Yhg1oAZ4+6hKPic/mQI6NaqXJSXMps5nFlwUuX2ewhtLZZDPg63TI042qGKBg==} + '@oxfmt/binding-linux-s390x-gnu@0.62.0': + resolution: {integrity: sha512-UyAFmyHkgSgUJ/wOM4p3U8AC2yAFvRH5PNBs7TnK0fObTT/XSWcdr/lAzPSWaekHaZFaMeFZyk9n93Joq3J93A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.52.0': - resolution: {integrity: sha512-IDO2loXK2OtTOhSPchU9MW25mWL2QCDGdJbjN8MXKZVS80qXe5gMTwQWu/gMJ3juoBHbkuUZNB2N1LHzNT7DoA==} + '@oxfmt/binding-linux-x64-gnu@0.62.0': + resolution: {integrity: sha512-1iYMP0leytWazFubD/WnINJuIrzRPuoL1aWEJdlGezEzDbTxcd29R4r8IUzP2oWeKst5V02uMJgR2NILlPlG6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.52.0': - resolution: {integrity: sha512-mAV2Hjn0SatJ+KoAzKUC3eJhdJ8wv+3m1KyuS0dTsbF0c5weq+QrCt/DRZZM+uj/XiKzCDEUKYsBF30e2qkcyw==} + '@oxfmt/binding-linux-x64-musl@0.62.0': + resolution: {integrity: sha512-4rA/URtJSTVNVAQz6Q8wf7SaRvOXVy+TizriT9hs/Y1XhLR/R+92uWKRQG8yFWRAIEBbFHJ6WevQcl/G9SXEfw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.52.0': - resolution: {integrity: sha512-vd4npaUIwChxp7XzkqmepBWTT9YMcSe/NBApVGPC30/lLyOVaV3dvma1SKo03t8O73BPRAG7EyJzGlN5cJM5hQ==} + '@oxfmt/binding-openharmony-arm64@0.62.0': + resolution: {integrity: sha512-mSZuFHU2ar1KLUjXpI2QBQcJ1VsOB3mOCgQXuXCpKs19dgh4u+OaovNfrWDfiJb+ihJ2+f7YFcaO9bS2dlTCXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.52.0': - resolution: {integrity: sha512-k2sz6gWQdMfh5HPpIS+Bw/0UEV/kaK2xuqJRrWL233sEHx9WLlsmvlPFM4HUNThkYbSN0U0vPW7LVKZWDS8hPQ==} + '@oxfmt/binding-win32-arm64-msvc@0.62.0': + resolution: {integrity: sha512-OfwuhkcjDlqC4EgDojtiV9mzpLqeB9KqTOWPOjLEYBVdDCVSxqW3qzp/xcIxsbtI0UgGCnKvAqYKyY25kf5JZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.52.0': - resolution: {integrity: sha512-rhke69GTcArodLHpjMTfNnvjTEBryDeZcUCKK/VjXDMtfTULl6QRh0ymX5/hbCUv2WjYm9h/QbW++q2vE15gWQ==} + '@oxfmt/binding-win32-ia32-msvc@0.62.0': + resolution: {integrity: sha512-P9uDDNFRzghO3X8QAzhkjKhK7JvtABsVn8UYtFX7uor12IAnwNt8nNIctvfWj1JkQU/kE+fmLRPiw7XlrIHsZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.52.0': - resolution: {integrity: sha512-q5xL7oeXkZdEtNZWBdvehJcmt+GRu9l2bK40yJs1jJXlqq+r0Hygb1rTjq+FM2o/2xyt4cufH6KRplHp3Jjsvw==} + '@oxfmt/binding-win32-x64-msvc@0.62.0': + resolution: {integrity: sha512-dlI5SY7XYQCiCBafntWagCR6HcAJB/NpsLtdlPx8x08+Osz8Ok1HHz1GZuusegCe/VoJ6pAnF5a4pd5OZAq7qQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.23.0': - resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==} + '@oxlint-tsgolint/darwin-arm64@7.0.2001': + resolution: {integrity: sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.23.0': - resolution: {integrity: sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==} + '@oxlint-tsgolint/darwin-x64@7.0.2001': + resolution: {integrity: sha512-pXfBb5BqONCcgrXQNUZWXgiYmRSWJzd97S8i41VVOh6ut0tyo+cJ5FKFpczDHxiVNfj/3e7c9B4MtztNdpIVCw==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.23.0': - resolution: {integrity: sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==} + '@oxlint-tsgolint/linux-arm64@7.0.2001': + resolution: {integrity: sha512-roP7zujb/QDPzDwEKsFFpzNHHy91/Y7oX9vQXk78ekyZtcQj1QXDIMH33gjDdHBfRl4K9pZ36xhRgrP4Zr+R8A==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.23.0': - resolution: {integrity: sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==} + '@oxlint-tsgolint/linux-x64@7.0.2001': + resolution: {integrity: sha512-UDezNqdECVmngu2TPnjaS1YoAmcTaBoI5lV9vk3VahBxoi+I5r9k3iJTT7qZoYWOXTD/7T7bNcwRgrocR6BscQ==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.23.0': - resolution: {integrity: sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==} + '@oxlint-tsgolint/win32-arm64@7.0.2001': + resolution: {integrity: sha512-uJZhqB6pdXLuN+AD1F5082byyQti/NPmJA77GtcFlmT2HzRelqbNls3SaIqxpjdFgvSBF9g0yOKGBkGFg7kX8Q==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.23.0': - resolution: {integrity: sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==} + '@oxlint-tsgolint/win32-x64@7.0.2001': + resolution: {integrity: sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog==} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.67.0': - resolution: {integrity: sha512-VrSi571rDv1N8HaEDM+DEX8nmT0y9jJo8tzzW13vsOWTx59xQczCIJx68n2zWOXRT5YKZsOZXp4qkHN/10x4mw==} + '@oxlint/binding-android-arm-eabi@1.77.0': + resolution: {integrity: sha512-E06sKWS6PiI6HRxS1wyQg22HvApt01hI7fV+T3wUk3OSbaaP4a3hYGY/MIQDmASqCiRjBdpRQYkgMkqH82cWmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.67.0': - resolution: {integrity: sha512-l6+NdYxMoRohix5r5bbigW16LPicceCwGcQ6LKKuE1kUdjgFfQolJjrJsQYPFetIs78Gxj/G/f5TEGoTCwj9nQ==} + '@oxlint/binding-android-arm64@1.77.0': + resolution: {integrity: sha512-NvsKz0KZxTp9cYWPLf+FXaSZwB3oO3peAjtukpOMBgse2vhQSoIIVqeO1yR0lEo/UcdZIDL18uq+kL0LzQ0ytA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.67.0': - resolution: {integrity: sha512-jOzXxS1AxFxhImLIRbtGIMrEwaXcgMw3gR57WB1cRk8ai+vpr6726kxXqVvlNsrXtJ/FrmOm8RxlC0m8SW24Qg==} + '@oxlint/binding-darwin-arm64@1.77.0': + resolution: {integrity: sha512-bgjTn6nW4bQCFBvSvuHCpDD+sONvmpo4lGI4PxzMt1quBA+xYxhczk6RiCn3GZ9gY8uhaBbwhj9MdKGfu6T9DA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.67.0': - resolution: {integrity: sha512-3DFAVY94OqjIZHXIPz37yGRSWwOFTAqChQ64/M69GYLawzP0KiwdhDNfqdKKYT0bTR/DNxmMnQsj3ns+8+X/Lg==} + '@oxlint/binding-darwin-x64@1.77.0': + resolution: {integrity: sha512-aotaIttH1R6j1Rwhx0M0htgeZyGtVQqYNTVEYMN/UcgHPquGA6kmk9OyuDc3a2GKUQBC+3C3GVQCcrRPMYqAFA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.67.0': - resolution: {integrity: sha512-e4dDKZuLu8TR9DEBssWSDahlPgZBwojTTHZUvnjBRJfJJbpxYCjfjKfi0Z1+CSLMiJBwI2yCDtRM1XJQaARjmg==} + '@oxlint/binding-freebsd-x64@1.77.0': + resolution: {integrity: sha512-nNx/wta7ksRAdYvq+l4AWjXkLxEXHALhENxjj2cYbQAIR4ybaA5L+hCbE63HOmft5czQ6ks+hb8vmEAnn7YGPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.67.0': - resolution: {integrity: sha512-BKytFdcQzbITV3xlnzDUDTEDtbUMCCiC4EaNTDZ4FyT8gdNvBC4gfiLucXp/sQl0XU3p7syTlorUWVVVBZab2g==} + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': + resolution: {integrity: sha512-tMLLjM7xXtzXisVCzkOTXNCy9bZVId2wteNwjohlFDR/jY6WagpEDA1c1wu4xRc20Hojaxj+V6DSR7gbKxijWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.67.0': - resolution: {integrity: sha512-XYAv0esBDX7BpTzRDjVX2Vdj+zndd8ll2dFQiaeQ6zTZr7A8GRDTN7fH3FP3jU+O0vCDx85oH/EtG7BzPgAXuw==} + '@oxlint/binding-linux-arm-musleabihf@1.77.0': + resolution: {integrity: sha512-MiAFDFaqR0tmHTAyo0YDcZ5hyLREdYw/RQhc2R3cbT+8O3tB+zqPM2th9TTQ+Uo3jn/embS+DO+HyX9ztCPkOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.67.0': - resolution: {integrity: sha512-zizRMjA0i6u/2B0evgda04iycu+MoNuf1pBy6Eh+1CjC5wMEG7qN5zdDKTCvFc0KSYSDM9QTG3gjZHirgtQuKg==} + '@oxlint/binding-linux-arm64-gnu@1.77.0': + resolution: {integrity: sha512-/xqQ3B16i1T4cyt/9Mn+4CpzhUXoBXp7kVpIwzOXNFLj5JmK1bIjsbSnX296Gg8A/o7oDtKWikFgBx0SLwztkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.67.0': - resolution: {integrity: sha512-zB/Tf6sUjmmvvbva9Gj3JTJ8rJ9t4I8/U0o6vSRtd0DRIsIuyegBwJAzhSUFQHdMijIRJkW0exs/yBhpw2S20w==} + '@oxlint/binding-linux-arm64-musl@1.77.0': + resolution: {integrity: sha512-LSbwuRKiNCenPDcbARqAZ5RfBy7gmj7vOvfJRLeCDU3gFtSxWbhv/+VTlaUqzUhNj1gFLHB8h7ALnxa/Az6z6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.67.0': - resolution: {integrity: sha512-kgU40Gt74CK0TCsF51KZymkIwN9U0BajKsMijB52zPqOeZU9NAHkA/NSQkZDHEaCakx42DxhXkODiAqf2b4Gug==} + '@oxlint/binding-linux-ppc64-gnu@1.77.0': + resolution: {integrity: sha512-QWdcH31mXEUe5Nq1s0CfCpceaKjIo9uZtwDjAuL681g1axf+5x8xrg/eXWaw//4NCxYZ4V4e5Hu5tvdR+pTBlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.67.0': - resolution: {integrity: sha512-tOYhkk/iaG9aD3FvGpBFd1Lrw0x0RaVoJBxjUkfNzS50rC5NS5BteNCwgr8A2zCdADrIIoze6D7u6U5Ic++/iQ==} + '@oxlint/binding-linux-riscv64-gnu@1.77.0': + resolution: {integrity: sha512-GnOfYgJxbcElOiPZaDFDl406ONddwvOWk2jvAAAEjwAl4GofNoHF+/HHUIBYa6bFCArlcGPi0XjC4cU1pkgF/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.67.0': - resolution: {integrity: sha512-sEtywrPb+0b+tHYl1SDCrw903fiC4eyKoNqzP3v+f2JT3Xcv4NEYG+P8rj+eEnX7IWhqV/xj8/JmcmVj21CXaA==} + '@oxlint/binding-linux-riscv64-musl@1.77.0': + resolution: {integrity: sha512-AyEMTUCf0xY+hHF+IxqXFQIX0yQOIR8ykpY0lJNOw9xYqOzUX8dyZfRvlG0RfXwuQn2eonf/8NrMmDSZJjdqsA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.67.0': - resolution: {integrity: sha512-BvR8Moa0zCLxroOx4vZaZN9nUfwAUpSTwjZdxZyKy4bv3PrzrXrxKR/ZQ0L9wNSvlPhnMJeZfa3q5w6ZCTuN6Q==} + '@oxlint/binding-linux-s390x-gnu@1.77.0': + resolution: {integrity: sha512-sPLzEcNvxd/oyVQ5oZo92CiHkFkpBeRop13E/P3TPY+hZfXHKCOWKI70TE2RYwMKFJDc20EMjH16L7NZICtKTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.67.0': - resolution: {integrity: sha512-mm2cxM6fksOpq6l0uFws8BUGKAR4dNa/cZCn37Npq7PFbhD5HDJqWfnoIvTaeRKMy5XdS2tO0MA0qbHDrnXAAA==} + '@oxlint/binding-linux-x64-gnu@1.77.0': + resolution: {integrity: sha512-1Oh2ssH2L7lwyvkdSqaMUfsGfwU2Wfvew+obBUYjRVqhpBcUpwnsPSEr1IzVi9XqkuY10geiLsNKecqaZC34Dw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.67.0': - resolution: {integrity: sha512-WmbMuLapKyDlobMkXAaAL0Y+Uczh4LETfIfQsUpbId4Ip8Ai82/jqeYTOoUCkuuhBFapgqP253+d83tLKOksJg==} + '@oxlint/binding-linux-x64-musl@1.77.0': + resolution: {integrity: sha512-0j/2wRgNGO+Qj/M1uu/p57h/hFTTWWcfie0ufkbabeus2s5+/QqkCflnMOwLLN5m2GsNeWp4xdl4cPa4n7QCOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.67.0': - resolution: {integrity: sha512-9g/PqxYJelzzTAOR5Y+RiRqdeydhEuXv2KxNeFcAKQ7UsvnWSY1OP4MsuPMbTO2Pf70tz7mFhl1j13H3fyh+8g==} + '@oxlint/binding-openharmony-arm64@1.77.0': + resolution: {integrity: sha512-BJ/j54qS0usEnyDkLYURMj2iiD9h5Cyy+ppzeMSXBGRXaGRNWnj1Mw14NqWMR5E/PzdgB30OOCCzLzbRoduafw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.67.0': - resolution: {integrity: sha512-2VhwE6Gatb0vJGnN0TBuQMbKCOiZlSQ/zJvVWYLK4a9d4iDiJOen/yVQkGpmsJ90MuH66fzi0kEKI0jRQMDxGA==} + '@oxlint/binding-win32-arm64-msvc@1.77.0': + resolution: {integrity: sha512-Yh8w+g2Lpx7StrvtYkoz9JJvXjB9wxgFChFNb85nrXm/wj/XTwGWS1hve9+900HL7llrntYB3YP+y32E3tRqzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.67.0': - resolution: {integrity: sha512-EQ3VExXfeM1InbE5+JjufhZZTWy+kHUwgt3yZR7gQ47Je/mE0WspQPan0OJznh493L5anM210YNJtH1PXjTSFg==} + '@oxlint/binding-win32-ia32-msvc@1.77.0': + resolution: {integrity: sha512-zja5b7+6a7UsRFgAQSrnax5vrzliEyNPLCjfXONu/vTWswaIVZGFajJZptaeRvPE4LghtFdAzVFlexTm7MVTGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.67.0': - resolution: {integrity: sha512-bw24y+/1MHS4QDkons3YyHkPT9uCMoLHHgQhb+mb8NOjTYwub1CZ+K9Ngr8aO5DMrDrkqHwTzlTwFP2vS8Y/ZQ==} + '@oxlint/binding-win32-x64-msvc@1.77.0': + resolution: {integrity: sha512-+teyvPDZ2RjUvo+SuCqS/UhaJl1QtdW5fWT5NJTV61V5MIuIS90Db9LixmtEGvXixyttiK62P96MSu3UlpviBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/plugins@1.61.0': - resolution: {integrity: sha512-nkOyZEF1vH527CkdQtOp1HMrVFEM4ResURvI2JFeGoup+h+43J/k/FgdOR9b9Isxg+Yae7qVDa7y3nssE8b3TQ==} + '@oxlint/plugins@1.73.0': + resolution: {integrity: sha512-OhgMQeMmZA0dcFcX4/priaJZWdFECxiClgq6mRX6aatZEcV9PbKC3P3/v8U1hVjviT1i5U+vR8lAtBV6m4FXAA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@pkgjs/parseargs@0.11.0': @@ -8206,6 +8561,9 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/primitive@1.1.7': + resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} + '@radix-ui/react-accessible-icon@1.1.7': resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} peerDependencies: @@ -8245,6 +8603,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-arrow@1.1.15': + resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: @@ -8332,6 +8703,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context-menu@2.2.16': resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} peerDependencies: @@ -8354,6 +8734,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.2.2': + resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.1.15': resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} peerDependencies: @@ -8389,6 +8778,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.1.19': + resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-dropdown-menu@2.1.16': resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} peerDependencies: @@ -8411,6 +8813,28 @@ packages: '@types/react': optional: true + '@radix-ui/react-focus-guards@1.1.6': + resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.16': + resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-scope@1.1.7': resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: @@ -8459,6 +8883,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-label@2.1.7': resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} peerDependencies: @@ -8563,6 +8996,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.23': + resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -8576,6 +9022,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popper@1.3.7': + resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.17': + resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.9': resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: @@ -8589,6 +9061,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.10': + resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.1.5': resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: @@ -8602,6 +9087,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.10': + resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: @@ -8750,6 +9248,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.3.3': + resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-switch@1.2.6': resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} peerDependencies: @@ -8850,6 +9357,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.4': + resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-controllable-state@1.2.2': resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: @@ -8859,6 +9375,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.6': + resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-effect-event@0.0.2': resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: @@ -8868,6 +9393,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-escape-keydown@1.1.1': resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: @@ -8895,6 +9429,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-previous@1.1.1': resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} peerDependencies: @@ -8913,6 +9456,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-rect@1.1.4': + resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-size@1.1.1': resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} peerDependencies: @@ -8922,6 +9474,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-size@1.1.4': + resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-visually-hidden@1.2.3': resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} peerDependencies: @@ -8938,6 +9499,9 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@radix-ui/rect@1.1.3': + resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} + '@react-email/body@0.3.0': resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} engines: {node: '>=20.0.0'} @@ -9124,48 +9688,54 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-pdf/fns@3.1.2': - resolution: {integrity: sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==} + '@react-pdf/fns@3.1.3': + resolution: {integrity: sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w==} - '@react-pdf/font@4.0.4': - resolution: {integrity: sha512-8YtgGtL511txIEc9AjiilpZ7yjid8uCd8OGUl6jaL3LIHnrToUupSN4IzsMQpVTCMYiDLFnDNQzpZsOYtRS/Pg==} + '@react-pdf/font@4.0.8': + resolution: {integrity: sha512-deNd+emtZAJho1IlzKL9bRoLAGv/6oXOIKO2oZfs4RuXUrK1onLHbJO7e2YoVLPFP/sQxisRTnzdJFtd35iKwA==} - '@react-pdf/image@3.0.4': - resolution: {integrity: sha512-z0ogVQE0bKqgXQ5smgzIU857rLV7bMgVdrYsu3UfXDDLSzI7QPvzf6MFTFllX6Dx2rcsF13E01dqKPtJEM799g==} + '@react-pdf/image@3.1.0': + resolution: {integrity: sha512-ks7Ry8v711r8NvKWSELehj0BXBNPRihSnWsM09nDD8Ur175zbWBCK217LLwQMKDNYDVpkZaipdoJPom1LGaE9g==} - '@react-pdf/layout@4.4.2': - resolution: {integrity: sha512-gNu2oh8MiGR+NJZYTJ4c4q0nWCESBI6rKFiodVhE7OeVAjtzZzd6l65wsN7HXdWJqOZD3ttD97iE+tf5SOd/Yg==} + '@react-pdf/layout@4.6.1': + resolution: {integrity: sha512-gN6PmWoEffvlIkifLfEhMsVucRywVMyH3rnxdyOVOhGy0nWJKKGpHyPc4plbDdpP6EfZ0r8prHXujDSkIG2nSA==} - '@react-pdf/pdfkit@4.1.0': - resolution: {integrity: sha512-Wm/IOAv0h/U5Ra94c/PltFJGcpTUd/fwVMVeFD6X9tTTPCttIwg0teRG1Lqq617J8K4W7jpL/B0HTH0mjp3QpQ==} + '@react-pdf/math@2.0.1': + resolution: {integrity: sha512-o+QQA03iT76RiKe8uhOgof+5X7GzA4SUjjPbffi6IGwcOotuxTKm3uhZAnx18ioylSpJf4BcVgUazzb5Ycv8qw==} + peerDependencies: + '@react-pdf/renderer': '>=4.5.1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@react-pdf/png-js@3.0.0': - resolution: {integrity: sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==} + '@react-pdf/pdfkit@5.1.1': + resolution: {integrity: sha512-wNcdSsNlNYyGHGAgIdt453egBF7fiF9UxpRlklUfVvu8OWCrUppG9xiUrPLVoKiqWet5tMi0w6LmuFUJuYqjEg==} - '@react-pdf/primitives@4.1.1': - resolution: {integrity: sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==} + '@react-pdf/primitives@4.3.0': + resolution: {integrity: sha512-nYXoZ36pvwNzbc54+DbL8RCn15jU7woJ9D/svnh5tpUXekJ+CbI4mZLo6boSv24CvJgychOu6h7gxX03B4ps0A==} '@react-pdf/reconciler@2.0.0': resolution: {integrity: sha512-7zaPRujpbHSmCpIrZ+b9HSTJHthcVZzX0Wx7RzvQGsGBUbHP4p6s5itXrAIOuQuPvDepoHGNOvf6xUuMVvdoyw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@react-pdf/render@4.3.2': - resolution: {integrity: sha512-el5KYM1sH/PKcO4tRCIm8/AIEmhtraaONbwCrBhFdehoGv6JtgnXiMxHGAvZbI5kEg051GbyP+XIU6f6YbOu6Q==} + '@react-pdf/render@4.5.1': + resolution: {integrity: sha512-IW/N4HWJWtioBXCf7n02IR24VJJ8gbdS3jGypf+vW/rSErEx3/URRzh9UK6Ma8Fpog9+T/W6GE2NHJ5AAKHhVA==} - '@react-pdf/renderer@4.3.2': - resolution: {integrity: sha512-EhPkj35gO9rXIyyx29W3j3axemvVY5RigMmlK4/6Ku0pXB8z9PEE/sz4ZBOShu2uot6V4xiCR3aG+t9IjJJlBQ==} + '@react-pdf/renderer@4.5.1': + resolution: {integrity: sha512-5r1VQrE6FRLXX5wWUxwZzM24E2BJMo6g8AQWuS8WyPs9ugu5yMnb2g8/RpPYka/Z6J+RUEWc32wty2NoUJF42Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@react-pdf/stylesheet@6.1.2': - resolution: {integrity: sha512-E3ftGRYUQGKiN3JOgtGsLDo0hGekA6dmkmi/MYACytmPTKxQRBSO3126MebmCq+t1rgU9uRlREIEawJ+8nzSbw==} + '@react-pdf/stylesheet@6.2.1': + resolution: {integrity: sha512-2+UEk+7e+z8baaWi2l5kPLWmwtJeOI+T5wW9GGeN3iDH7vd3kbTqOpN1yt9mmfNVZFxQsnDHpznFb5v5UF983A==} + + '@react-pdf/svg@1.1.0': + resolution: {integrity: sha512-cTIHXiz9x1HrbfqzfxfZP3FRdDwUXG77QWF6Fb5MP/lV3ONxR+g0Z3hwtBatCS9HeGBQCpxX/Lzb8wHE+co1PA==} - '@react-pdf/textkit@6.1.0': - resolution: {integrity: sha512-sFlzDC9CDFrJsnL3B/+NHrk9+Advqk7iJZIStiYQDdskbow8GF/AGYrpIk+vWSnh35YxaGbHkqXq53XOxnyrjQ==} + '@react-pdf/textkit@6.3.0': + resolution: {integrity: sha512-v6+V8nAcVwm7s2s1jIG2MD3Iw//x/k+XrH1foWOELBE4b32pyDgKyPXN/6KJE0dnX7+fVy27uctLNCLNMvzKzQ==} - '@react-pdf/types@2.9.2': - resolution: {integrity: sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==} + '@react-pdf/types@2.11.1': + resolution: {integrity: sha512-i9xQgfaDU9QoeNnbp6rltXCWg1huEh195rpOuN8cE4BZ2FuLdQrsIcb2dhFF9aOxXf+XBA6LOSpIW051MDD/bw==} '@reduxjs/toolkit@2.11.2': resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} @@ -9602,43 +10172,40 @@ packages: resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} engines: {node: '>=20'} - '@shikijs/engine-javascript@3.23.0': - resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} - - '@shikijs/engine-javascript@4.0.2': - resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==} + '@shikijs/core@4.4.3': + resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} engines: {node: '>=20'} - '@shikijs/engine-oniguruma@3.23.0': - resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} - - '@shikijs/engine-oniguruma@4.0.2': - resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==} + '@shikijs/engine-javascript@4.4.3': + resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} engines: {node: '>=20'} - '@shikijs/langs-precompiled@4.0.2': - resolution: {integrity: sha512-I7uqbU58tSTgChNtu7dTnJWOo0lAsZMyv1RT9DCb+qlcQu5fkp2lAeISo+2qxunYSX+l81nI83lYp75OoqYzqg==} + '@shikijs/engine-oniguruma@4.4.3': + resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} engines: {node: '>=20'} - '@shikijs/langs@3.23.0': - resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + '@shikijs/langs-precompiled@4.4.3': + resolution: {integrity: sha512-i3+91QcqVBji2mlCinQpHQyoZfciaWsHCuv5XZwMXGEmSAMCW0oCdrWp+zMKLf0CwHd9XT+E5RBDJ0RY1USWCw==} + engines: {node: '>=20'} - '@shikijs/langs@4.0.2': - resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==} + '@shikijs/langs@4.4.3': + resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} engines: {node: '>=20'} '@shikijs/primitive@4.0.2': resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==} engines: {node: '>=20'} - '@shikijs/rehype@3.23.0': - resolution: {integrity: sha512-GepKJxXHbXFfAkiZZZ+4V7x71Lw3s0ALYmydUxJRdvpKjSx9FOMSaunv6WRLFBXR6qjYerUq1YZQno+2gLEPwA==} + '@shikijs/primitive@4.4.3': + resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} + engines: {node: '>=20'} - '@shikijs/themes@3.23.0': - resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + '@shikijs/rehype@4.4.3': + resolution: {integrity: sha512-vkG9jG1aRnrx05R31uAOKQHE8qpY7r1cBXE2sAZgFK2IaPnQHwaP4L1C6amQixmZ8thBsKwfbSsYxMDoo46UWQ==} + engines: {node: '>=20'} - '@shikijs/themes@4.0.2': - resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==} + '@shikijs/themes@4.4.3': + resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} engines: {node: '>=20'} '@shikijs/transformers@3.23.0': @@ -9650,19 +10217,13 @@ packages: peerDependencies: typescript: '>=5.5.0' - '@shikijs/types@3.23.0': - resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} - - '@shikijs/types@4.0.2': - resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==} + '@shikijs/types@4.4.3': + resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} engines: {node: '>=20'} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sinclair/typebox@0.34.49': - resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} - '@smithy/chunked-blob-reader-native@4.2.3': resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} engines: {node: '>=18.0.0'} @@ -10035,6 +10596,12 @@ packages: '@types/react-dom': optional: true + '@testing-library/user-event@14.6.3': + resolution: {integrity: sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@tiptap/core@3.29.2': resolution: {integrity: sha512-oKUkiPUB7noilVYxI9lNzUD4rX17sHub+PYjMfHMWHG9A3nvIy+FdePIVIIhThKWF7ijhr3eIqHY51Bn+GAFtw==} peerDependencies: @@ -10135,30 +10702,96 @@ packages: '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + '@types/d3-ease@3.0.2': resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} '@types/d3-path@3.1.1': resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + '@types/d3-scale@4.0.9': resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + '@types/d3-shape@3.1.8': resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + '@types/d3-time@3.0.4': resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -10180,23 +10813,11 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/jest-image-snapshot@6.4.1': - resolution: {integrity: sha512-pj3Sdc7Cx5mMLUttPprazSDQCur2cr512Dm38e9aAHI55LDxEhqdyqzK9myC4EmEy7sPAF2nGJ8zifX4qso7sQ==} - - '@types/jest@30.0.0': - resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} '@types/jsdom@21.1.7': resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} @@ -10210,6 +10831,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/lodash.foreach@4.5.9': resolution: {integrity: sha512-vmq0p/FK66PsALXRmK/qsnlLlCpnudvozWYrxJImHujHhXMADdeoPEY10zwmu26437w85wCvdxUqpFi+ALtkiQ==} @@ -10237,12 +10861,6 @@ packages: '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} - '@types/node@20.19.39': - resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} - - '@types/node@22.13.13': - resolution: {integrity: sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==} - '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} @@ -10261,9 +10879,6 @@ packages: '@types/pg@8.20.0': resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} - '@types/pixelmatch@5.2.6': - resolution: {integrity: sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==} - '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} @@ -10283,9 +10898,6 @@ packages: '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -10295,6 +10907,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -10310,72 +10925,139 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.35': - resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-EHrtoVGEEhIhsnGe+b8w0FoM9JfIw5SkoPwO8ifaU0PrYm2UbyPbj2I2hOTxtk458t0irvGz2+8cshylBRbKng==} + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-BcDA56hkk6mrUpysaOVvrdACoX5d2SF1JTwHMoNjT1KysBicExS2wlH0eN0L01iDeqtB73XHl7A4zrKFmKzrBg==} + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-TDAlBpyYCF7Z+ELTH+1tabDE6W3shl+H+Z+nmzaQio1I8pFvbwt2iLlE0Rc9CpRdIeaqr0ppMEgXHoeV3fZFWA==} + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-YRXRS/7ZqrDKXBJhFQcZiOdnHRuRbWoL/QkggpoGfhIuSAh4HvTU0WEUnPM+jRnH2kUMfsSgd8EAnPvmuP7/VA==} + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-/QDmRWt6abB7fw3yMchjlyDXej/7Dr8mYG4wvGGf6c1hc5Il5UpDQWNHejatdeKvAu+sszz8JhTuL8et47fTTg==} + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} engines: {node: '>=16.20.0'} - cpu: [x64] + cpu: [loong64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-WTJOLoe2rxT0W1i8ndWk2MKrakxRFNki537JZxvKAmSTbyOZznHlW3O3dbryUtTBYA716DDqS3ci24kuIfvBdg==} + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] + cpu: [mips64el] + os: [linux] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-4cSCpXG7um18nwmLdU/SjoTv3OcO38/ufTiy1oWVccgGHLJqppiOP9/o+ElKIWhvrp78IaGy8+h3YqEjQ4/pcQ==} + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] + cpu: [ppc64] + os: [linux] - '@typescript/native-preview@7.0.0-dev.20260615.1': - resolution: {integrity: sha512-JJ8X1l7H1GrnseK1k30qfQqB8Pz6jw3IALZVIj5oXQeRbUCe0Wx3ljkJEmOpunogdhEfA8IlOggskbUjVsXKBQ==} + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} engines: {node: '>=16.20.0'} - hasBin: true + cpu: [riscv64] + os: [linux] - '@typescript/vfs@1.6.4': - resolution: {integrity: sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==} - peerDependencies: - typescript: '*' + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] - '@uppy/companion-client@3.8.2': - resolution: {integrity: sha512-WLjZ0Y6Fe7lzwU1YPvvQ/YqooejcgIZkT2TC39xr+QQ7Y1FwJECsyUdlKwgi1ee8TNpjoCrj3Q1Hjel/+p0VhA==} - peerDependencies: - '@uppy/core': ^3.13.1 + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@typescript/vfs@1.6.4': + resolution: {integrity: sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==} + peerDependencies: + typescript: '*' + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher + + '@uppy/companion-client@3.8.2': + resolution: {integrity: sha512-WLjZ0Y6Fe7lzwU1YPvvQ/YqooejcgIZkT2TC39xr+QQ7Y1FwJECsyUdlKwgi1ee8TNpjoCrj3Q1Hjel/+p0VhA==} + peerDependencies: + '@uppy/core': ^3.13.1 '@uppy/core@3.13.1': resolution: {integrity: sha512-iQGAUO4ziQRpfv7kix6tO6JOWqjI0K4vt8AynvHWzDPZxYSba3zd6RojGNPsYWSR7Xv+dRXYx+GU8oTiK1FRUA==} @@ -10468,6 +11150,9 @@ packages: peerDependencies: '@uppy/core': ^3.13.0 + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vercel/analytics@1.6.1': resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} peerDependencies: @@ -10524,11 +11209,27 @@ packages: babel-plugin-react-compiler: optional: true - '@vitest/expect@4.1.7': - resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} + '@vitest/browser-playwright@4.1.10': + resolution: {integrity: sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==} + peerDependencies: + playwright: '*' + vitest: 4.1.10 + + '@vitest/browser-preview@4.1.10': + resolution: {integrity: sha512-14MJrL59ZFkqXLjwfSk6RzTDy5Czf9UG4+8q8L6Gxjs2aPjEce/cVNYV14bXAc2BvMjUNu904+ZEZA1Xc1wtvQ==} + peerDependencies: + vitest: 4.1.10 + + '@vitest/browser@4.1.10': + resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==} + peerDependencies: + vitest: 4.1.10 + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - '@vitest/mocker@4.1.7': - resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -10538,41 +11239,39 @@ packages: vite: optional: true + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + '@vitest/pretty-format@4.1.5': resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} - '@vitest/pretty-format@4.1.7': - resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/runner@4.1.7': - resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/snapshot@4.1.7': - resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} - - '@vitest/spy@4.1.7': - resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} '@vitest/ui@4.1.5': resolution: {integrity: sha512-3Z9HNFiV0IF1fk0JPiK+7kE1GcaIPefQQIBYur6PM5yFIq6agys3uqP/0t966e1wXfmjbRCHDe7qW236Xjwnag==} peerDependencies: - vitest: 4.1.7 + vitest: 4.1.10 + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} '@vitest/utils@4.1.5': resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} - '@vitest/utils@4.1.7': - resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} - - '@voidzero-dev/vite-plus-core@0.1.24': - resolution: {integrity: sha512-iXPGBABnQnrDMx89H6MOCGcTZp+QW+3rY4YMVKdE6ydchSvPk2O3MI2vgaRVfOtWJ2IjnxSnf1n2yjP67ZBRFQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-core@0.2.9': + resolution: {integrity: sha512-dWqScAAwa8h/i9jCiGAMs7YarzQWInHZ5gCJNbQkHXA6Zp6A2T2anN9YMFVPpb7CwVFwkI2iPF5yl/DXtq+zUA==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} peerDependencies: '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.22.1 - '@tsdown/exe': 0.22.1 - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@types/node': ^25.6.0 + '@vitejs/devtools': ^0.4.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -10583,17 +11282,13 @@ packages: sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 - typescript: ^5.0.0 || ^6.0.0 + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 unplugin-unused: ^0.5.0 unrun: '*' yaml: ^2.4.2 peerDependenciesMeta: '@arethetypeswrong/core': optional: true - '@tsdown/css': - optional: true - '@tsdown/exe': - optional: true '@types/node': optional: true '@vitejs/devtools': @@ -10627,86 +11322,55 @@ packages: yaml: optional: true - '@voidzero-dev/vite-plus-darwin-arm64@0.1.24': - resolution: {integrity: sha512-Hpo9W9piSFlEsJzGkwzfDXhJGrnYByxHXF7NVQZ7g+SLOprddtlfTeM8t+gq9dxcuq0RzM8ddMAhDQP/K3fZQA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-darwin-arm64@0.2.9': + resolution: {integrity: sha512-/qJHqMfyy/LiCJk4UYZfFW6Comsfm8zDuy4P8UFWnFqRjmefYvZbMK4ThYNM87tKNWYWjsnClzssjJOmDTAc8w==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [darwin] - '@voidzero-dev/vite-plus-darwin-x64@0.1.24': - resolution: {integrity: sha512-SwnnnZrEFBiU5iKlh/CZAVwn0RFt/Udrvt3kFLtdRxMtN5bKaqTFVA2H8Y/FPCWp1QX9bs4V9ZIAeXAk06zLkw==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-darwin-x64@0.2.9': + resolution: {integrity: sha512-3MGnNeazgYAqQTaC7JIbZyrTHmxSXTWFr9G1yAdeItKasB1R8HKIkCS1Qnr49Ge4S/cyOODivdbcpqFkrT93ng==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [darwin] - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.24': - resolution: {integrity: sha512-ImM3eqDki4DpRuHjW6dEh4St8zvbcfOMR7KQZJX42ArriCLQ/QdaYhDRRbcDi27XsOBqRxm2eqUUEymPrYIHpA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.9': + resolution: {integrity: sha512-6LmukER8qD4UBIRqMNv4Ilq7CxfRhngLUXlMv8vbTupeLRWPJSKvKEHyRxCwr6JP57Gxfr8KrX1ye42WzcZF0g==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-arm64-musl@0.1.24': - resolution: {integrity: sha512-gj4mzbob/ls8Zs7iTuF9Gr0EFFF7tdpDiPxDPBkH8tJP5OkHABlzWUwJhU+9xxcUbTaXqpHDw68Mil7jm5dpMg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.9': + resolution: {integrity: sha512-cBs626GWkyJlwKP0nsdHlMWpuTl9xOWRxAUoqtxXPtw80bVy4WM5eNS4SXPC5pX10jR7DRIkRpzNAsy7fv8Faw==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.24': - resolution: {integrity: sha512-x7IYK7lI+WuF1n3jSzEYU6FgJxPX/R0rDmTTsOutooGGCU7uShZvfZqIoiTXK0eFnJU5ij5BfBgenenUfsaT/A==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.9': + resolution: {integrity: sha512-2Iy8x4PCPMNzXeu3pREevlggoeK8PwtdUiCpoSybAZBtR/aqMxsJREyt/eKv45F8lsiNlA8PbIWEvPxLSgzFLQ==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-x64-musl@0.1.24': - resolution: {integrity: sha512-JCy2w0eSVUlWQlggK5T47MnL+j0o4EY7hLskINVI8gi+aixQF4xnYBDobz0lbxkqz3/IfiLyXUx6TcU3thcsGQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-linux-x64-musl@0.2.9': + resolution: {integrity: sha512-zuGx+eRotWPd9cmh1X9AfsC2tN/Ad9Hk6LAzlxoKJUjEkTsY3WjVJ6Da0z48SAUFuenI0JkdqXnMCrePtGvWHg==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-test@0.1.24': - resolution: {integrity: sha512-9NiG6UadG0iOaPL1AMsO5sDKkx6MADHw4/mMOmHWZUhhUwqzfVtnnptMK37vD71e6KyR7yAscx19FrtOWWtjvA==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/coverage-istanbul': 4.1.8 - '@vitest/coverage-v8': 4.1.8 - '@vitest/ui': 4.1.8 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.24': - resolution: {integrity: sha512-G+/lhLKVjyn3FmgXX8jeWgq7RcE5O1kdR7QyFayQOdlMX/ZRkvUwQD7bFaqhKzgJM6Oj3a1FH3HQPYk5QOYuCQ==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.9': + resolution: {integrity: sha512-kaKb5Q8ReYTBfvLgUhdpLJG3aoNF8HOVJpf8qBm+THsd4WRrkRwsBHp2ITsU8oXl2gnDjFGhPDaURegd/z6Wxw==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [win32] - '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.24': - resolution: {integrity: sha512-b0e5XohEV1w/RdzAtv8/Hm6tvHPXouPtBNsljjW/lDJZq3NCLND5s6lqe8H4IenrgmKSoqakHWtlqJqM36cFbw==} - engines: {node: ^20.19.0 || >=22.12.0} + '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.9': + resolution: {integrity: sha512-/fEk3gbQTJknCiYM/GTL/L++Azsav8rCAjmtKrjmCbqEif5IMzqTfvM68n+PiLB3JVoQJGF/mg2niODr0IE/2w==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [win32] @@ -10755,6 +11419,11 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@xmldom/xmldom@0.9.10': + resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} + engines: {node: '>=14.6'} + deprecated: this version has critical issues, please update to the latest version + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -10800,6 +11469,131 @@ packages: resolution: {integrity: sha512-5IBr9puk4BL0ke09Yqa0uFNIpCreUocCPxHMQkF1uJvQfwDx60kRm0sYptHLTUcnNtxiCG3wqkogzQbQFszXgQ==} engines: {node: '>=22.0.0', npm: '>=8.0.0'} + '@yuku-codegen/binding-darwin-arm64@0.5.48': + resolution: {integrity: sha512-yo96Oef12WzqnphInfz/eexVse3+kWgfGS5g2S3rFS3dcGn1ENW9xLFDZUP9rh+yP76DOq38wBoFi1+I9+6qBg==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.5.48': + resolution: {integrity: sha512-aRCTw0EZC4bVosmw//0OMYP5tGWFE0Cu5yUBFkUbhXx/iBzvORcJ2xPNlOp/vtCCo9Ys4vp8b0DigJV6uOVb2g==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.5.48': + resolution: {integrity: sha512-CA0AQAEApDkbw51PdLWMtKPJ41/7rvXsS3SJs+phG7fHJI+MuFzWuLbkucZfZoEOiDscmcsfYIdgL8BsfuyKKQ==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.5.48': + resolution: {integrity: sha512-DuSQlk8bH4gpmW3/00P0NLagAcMv8jOxjT40cQmxKRkktr+SUOALCfkT89tdDq3qtY95NR2GXOZ7AjNh7KKqCw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm-musl@0.5.48': + resolution: {integrity: sha512-bxj4Ee+wlaJcWJwft2ReJXWw5sfl1qavDz6+dlRdU1xfTEtjPSNiAWhiCHnJR0R4Ygd57DnzSQmAVGvFv6RcGw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-arm64-gnu@0.5.48': + resolution: {integrity: sha512-mk5JVWh+0JOe5ue8k17kbYX8uGBoKt3ZqoCyxNh4nYAAcX7+X1tFUiU7jbjctu4vHeejCBFSTdQ021+V31cUCQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm64-musl@0.5.48': + resolution: {integrity: sha512-4q3vkrNghbllyxOm2KesFLxCPKHF7r3JyQ7BWZccY1j2Y05yKoIFhoWCqIuQ2W/dpte9RI0+OVfwyxnrKg6fkA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-x64-gnu@0.5.48': + resolution: {integrity: sha512-csd4M1EVrGaohM8acM6gq1zpUA/Rwe2ulUMBKUcwQXm/k6n7cq1A++qdew78SOVb4do3JH1WE+WFwoGQAcWc1w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-x64-musl@0.5.48': + resolution: {integrity: sha512-KcDuEOT+GFoVKdvAWOv1v9iYjwnmvMZlO+j1Rw+5PYdeFLGWGzv/DD11y4SAAdwXIFcil4T0hibeIaF82WStMg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-win32-arm64@0.5.48': + resolution: {integrity: sha512-HI8qNrI8dWM5BuqIMKsqornRvTNFrE6sm5zToIJ9YIa9zt5+29P7fJ7Nr39EVf6dAWSb6q7JSpScJnRsQ+FgZA==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.5.48': + resolution: {integrity: sha512-X5YWJLO6EfBZpeBqO0AYESnUizbpFDWArcvVD61w0PEWQ3CaFRLnbQXs+kpM4ZZfGMfIE22zfA08QSY67q7TNQ==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-darwin-arm64@0.5.48': + resolution: {integrity: sha512-If8mb7HH3vqghJ2NNZ8SuHfhsnjVzOxJpB8xcNOXS5WjYrs2mUhHIh5KOIvK13hDOzh0htGeGK3A6MsiEqE7HQ==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.5.48': + resolution: {integrity: sha512-EimvPXfspzxf1K11eB6tCW5oiQEXB8g84T2wP1TwzQagdDKo33bkmmVF0B32vTIpXnk/Ifu5IB61izZ1MylljA==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.5.48': + resolution: {integrity: sha512-0GcUMrumLHheThY9r5Tp46gaZYzn0irWPS1Zba6WY+vVQfhUtzGiWgXxI6tuXX0N32kEaaEVRpkKctvo6Kx3aQ==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.5.48': + resolution: {integrity: sha512-8S5T5wjCC73dmmpQeZ49aYsSunIUM3D4Fc6rdK96c+Ayg/p3FmeSPF3xuLZHejcTmqJIIvnbfPlUF+rB6DITjQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.5.48': + resolution: {integrity: sha512-tTmbxvnUHcK2/crS9547vk2SMmsajH1yqJ8ltXhIuHJgqR1v+d9n9KT+kSayo/5CS76LegeYxhMFjEivBH2hFA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.5.48': + resolution: {integrity: sha512-KGYCBMqI2zfwyhgq5tpPVNe7jpUeYTBm8DhjdS+zqWNumde/PEC170QE5RHxcOAlsirIDeIUk0jqx+r/axoFSw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.5.48': + resolution: {integrity: sha512-2wTSMsCSXLTc2lZUjMAuU5X4cje55u205WJqfV5NWNF6j9pW/tXyxr15dJeekj8ziLqBXzIsj4DbRh4sY/WcjA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.5.48': + resolution: {integrity: sha512-d/6v9UnGglVu1WC2JQyv/5aWSi5fXZeGSlidCfmHp4+N65N1GDKUnFtys5MK5eAPeAjTgSHGGtOc/yCcKTlv3A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.5.48': + resolution: {integrity: sha512-gX19gw6u4ApPy7SYMPKfFlEkrtj6WlORvrTKK3sBQqjyV+8+mUAkQgxXNjHw4RnOiAmVYg7TOlZcg8d+Qqod9A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.5.48': + resolution: {integrity: sha512-w6cQQLbqj3Jcom5Q7ifm103NUOQ9d+Cb4VU5lkrZDjMnwVJ9Hzzg1vCQR7miJuF44vhCXldbme5UryE3giEKlA==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.5.48': + resolution: {integrity: sha512-4gO0HmG7fzFxrw1rs0dUdnnaY9YgennjETqDWrTSp7x9fmTUOAoN4VsMfP7YyliQeG1WJJHc55O+rOhmsLppow==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.5.43': + resolution: {integrity: sha512-kSpvPntnXw5+lYjO71ffBEnQ5ycQ74KGIYknh0TS4xeyCuBkOqxyJumxZkMhLBBUCLjDAbx2+Icnr3Zh4ftjpQ==} + '@zeit/schemas@2.36.0': resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} @@ -10807,9 +11601,6 @@ packages: resolution: {integrity: sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==} engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - abs-svg-path@0.1.1: resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==} @@ -10852,10 +11643,6 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - ai@6.0.5: resolution: {integrity: sha512-CKL3dDHedWskC6EY67LrULonZBU9vL+Bwa+xQEcprBhJfxpogntG3utjiAkYuy5ZQatyWk+SmWG8HLvcnhvbRg==} engines: {node: '>=18'} @@ -10919,17 +11706,9 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} - aproba@2.1.0: - resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} - arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} - are-we-there-yet@2.0.0: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -11061,7 +11840,7 @@ packages: react-dom: ^18.0.0 || ^19.0.0 solid-js: ^1.0.0 svelte: ^4.0.0 || ^5.0.0 - vitest: 4.1.7 + vitest: 4.1.10 vue: ^3.0.0 peerDependenciesMeta: '@lynx-js/react': @@ -11216,10 +11995,6 @@ packages: caniuse-lite@1.0.30001787: resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} - canvas@2.11.2: - resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} - engines: {node: '>=6'} - canvas@3.1.0: resolution: {integrity: sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==} engines: {node: ^18.12.0 || >= 20.9.0} @@ -11274,18 +12049,10 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -11352,12 +12119,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -11373,6 +12141,14 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -11406,9 +12182,6 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - content-disposition@0.5.2: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} @@ -11437,6 +12210,12 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -11451,10 +12230,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-js@4.2.0: - resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} - deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. - css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -11464,41 +12239,132 @@ packages: engines: {node: '>=4'} hasBin: true - cssstyle@4.6.0: - resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} - engines: {node: '>=18'} - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + d3-format@3.1.2: resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + d3-path@3.1.0: resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} engines: {node: '>=12'} + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + d3-shape@3.2.0: resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} engines: {node: '>=12'} @@ -11515,9 +12381,22 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} - data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} data-urls@7.0.0: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} @@ -11541,6 +12420,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + debounce-fn@6.0.0: resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} engines: {node: '>=18'} @@ -11583,10 +12465,6 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - decompress-response@4.2.1: - resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} - engines: {node: '>=8'} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -11630,13 +12508,13 @@ packages: defu@6.1.6: resolution: {integrity: sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -11685,6 +12563,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.4.12: + resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -11779,9 +12660,6 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} @@ -11824,10 +12702,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -11896,6 +12770,10 @@ packages: jiti: optional: true + esm@3.2.25: + resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} + engines: {node: '>=6'} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -11981,10 +12859,6 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - expect@30.3.0: - resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -12106,13 +12980,6 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -12218,7 +13085,7 @@ packages: fumadocs-core: ^16.7.0 fumadocs-ui: ^16.7.0 react: '*' - shiki: '*' + shiki: ^4.4.3 typescript: '*' peerDependenciesMeta: '@types/estree': @@ -12244,11 +13111,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gauge@3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} @@ -12280,10 +13142,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stdin@5.0.1: - resolution: {integrity: sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==} - engines: {node: '>=0.12.0'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -12324,10 +13182,6 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -12340,9 +13194,6 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - glur@1.1.2: - resolution: {integrity: sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -12358,6 +13209,9 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -12381,9 +13235,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} @@ -12422,10 +13273,6 @@ packages: hsl-to-rgb-for-reals@1.1.1: resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==} - html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} - html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -12446,18 +13293,10 @@ packages: htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -12507,14 +13346,13 @@ packages: resolution: {integrity: sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==} engines: {node: '>=18'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -12534,6 +13372,9 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} @@ -12551,9 +13392,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.4: - resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -12755,39 +13593,6 @@ packages: jay-peg@1.1.1: resolution: {integrity: sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==} - jest-diff@30.3.0: - resolution: {integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-image-snapshot@6.5.2: - resolution: {integrity: sha512-frenWThr5ddnnokcX5N4gwi41hA5TiUOdhv/JoGcJrOaktHjrk4/7XbiHKW52lgKX+vei6QkRlgM7fkYQ15nPg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - jest: '>=20 <31' - peerDependenciesMeta: - jest: - optional: true - - jest-matcher-utils@30.3.0: - resolution: {integrity: sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-message-util@30.3.0: - resolution: {integrity: sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-mock@30.3.0: - resolution: {integrity: sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-regex-util@30.0.1: - resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-util@30.3.0: - resolution: {integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -12810,6 +13615,9 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-md5@0.8.3: + resolution: {integrity: sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -12821,15 +13629,6 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 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 - jsdom@29.0.2: resolution: {integrity: sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} @@ -12880,9 +13679,16 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -12895,6 +13701,12 @@ packages: resolution: {integrity: sha512-r2clcf7HLWvDXaVUEvQymXJY4i3bSOIV3xsL/Upy3ZfSv5HeKsk9tsqbBptLvth5qHEIhxeHTA2jNLyQABkLBA==} engines: {node: '>=20.0.0'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -12962,30 +13774,60 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.32.0: resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.32.0: resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.32.0: resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.32.0: resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} @@ -12993,6 +13835,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} @@ -13000,6 +13849,13 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} @@ -13007,6 +13863,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} @@ -13014,22 +13877,45 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.32.0: resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.32.0: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + linebreak@1.1.0: resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} @@ -13044,6 +13930,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -13112,10 +14001,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -13137,10 +14022,22 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mathjax-full@3.2.2: + resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==} + deprecated: Version 4 replaces this package with the scoped package @mathjax/src + + mathml2omml@0.5.0: + resolution: {integrity: sha512-4eLs37a+TH+CL/M5XZZrlc75SNRJNPiZzIaeSSvH0UFCRaCdSYRHWvrlA7MUbeQks/z4sVhfg+GlcZhVVUHzqg==} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -13201,6 +14098,12 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + mermaid@11.16.0: + resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==} + + mhchemparser@4.2.1: + resolution: {integrity: sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -13341,10 +14244,6 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - mimic-response@2.1.0: - resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} - engines: {node: '>=8'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -13366,30 +14265,16 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + mj-context-menu@0.6.1: + resolution: {integrity: sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==} mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} @@ -13449,14 +14334,16 @@ packages: namespace-emitter@2.0.1: resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==} - nan@2.26.2: - resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} - nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@4.0.2: resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} engines: {node: ^14 || ^16 || >=18} @@ -13504,8 +14391,8 @@ packages: next-validate-link@1.6.4: resolution: {integrity: sha512-wR/VKyJlaTbUT5k1uujEnk6O616YtWGt52s9FJa3tRCQ2qL2TGFTAklXJ0QdX1NTAEeP6rGFOTtHEDwveFrc2g==} - next@16.2.7: - resolution: {integrity: sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w==} + next@16.3.0: + resolution: {integrity: sha512-NEdGOzH+08eTXMUp9UYkA99Nhi5N6Thrhc1jgFOQgfgnGK/dA2hRwBpXep+exdFQrnwlRf/3Wixyp8lLBUpE2A==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -13559,11 +14446,6 @@ packages: resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==} engines: {node: '>=6.0.0'} - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -13583,13 +14465,6 @@ packages: resolution: {integrity: sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. - - nwsapi@2.2.23: - resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} - nypm@0.6.2: resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} engines: {node: ^14.16.0 || >=16.10.0} @@ -13651,11 +14526,11 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - oniguruma-parser@0.12.1: - resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} - oniguruma-to-es@4.3.5: - resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} @@ -13683,8 +14558,8 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - oxfmt@0.52.0: - resolution: {integrity: sha512-nJlYM35F64zTDMecCNhoHNkf+D/eHv7xcjj9XDSj+bFAVtN93m7v8DQMdHd6nDG6Akf/kEYYHmDUBs2Dz27Sug==} + oxfmt@0.62.0: + resolution: {integrity: sha512-vxgGHTmnDU9j4CX7dDBLzxgmHxfda/yPcgJkGCMUSCwRmz+euo/V08xXLNgXTeqAB9Fhf3Pe2nO1RNKLCVgphQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -13696,16 +14571,16 @@ packages: vite-plus: optional: true - oxlint-tsgolint@0.23.0: - resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==} + oxlint-tsgolint@7.0.2001: + resolution: {integrity: sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==} hasBin: true - oxlint@1.67.0: - resolution: {integrity: sha512-blwwaHPdoH8piQ5/z0KHeoHFR7FZgl12WluKJfu4qFLPkZl6mK04PkLE45Fw1NxfBRSlh40Gu7MkxHUw++ociQ==} + oxlint@1.77.0: + resolution: {integrity: sha512-qnGh8XJHaQ0dprrDXNQZgS0FgjI6v+V3+X8DwmaV++5Aamy6jGKfDdQ1TUvhUxtmKFAbEf4/WeO5QZX+5WSngg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.22.1' + oxlint-tsgolint: '>=7.0.2001' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -13775,6 +14650,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -13783,10 +14661,6 @@ packages: resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} engines: {node: '>=14.0.0'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-is-inside@1.0.2: resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} @@ -13818,21 +14692,12 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - path2d@0.2.2: - resolution: {integrity: sha512-+vnG6S4dYcYxZd+CZxzXCNKdELYZSKfohrk98yajCo1PtRoDgCTrrwOvK1GT0UoAdVszagDVllQc0U1vaX4NUQ==} - engines: {node: '>=6'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pdf-to-img@4.5.0: - resolution: {integrity: sha512-GCM2n+aYiupQmyoOmuj/0Q3Tr0hJg9iFVGiOq79y1ePF2cTGzdbPFd3lNlH1IxvqcGgQzwyo7KfNvtmefBxIiQ==} - engines: {node: '>=18'} - hasBin: true - - pdfjs-dist@4.2.67: - resolution: {integrity: sha512-rJmuBDFpD7cqC8WIkQUEClyB4UAH05K4AsyewToMTp2gSy3Rrx8c1ydAVqlJlGv3yZSOrhEERQU/4ScQQFlLHA==} - engines: {node: '>=18'} + pdfjs-dist@4.10.38: + resolution: {integrity: sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==} + engines: {node: '>=20'} peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} @@ -13881,14 +14746,6 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - pixelmatch@5.3.0: - resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} - hasBin: true - - pixelmatch@7.2.0: - resolution: {integrity: sha512-xhcb4yHu9sM/G7foGzoLtXYcC0zHEaOXXjRKhGup0fw78Nf2Tkiapv4EQyMzrbcmQPsllAI7DbFY2UT7PlI9Pg==} - hasBin: true - pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} @@ -13902,18 +14759,19 @@ packages: engines: {node: '>=18'} hasBin: true - pngjs@3.4.0: - resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} - engines: {node: '>=4.0.0'} - - pngjs@6.0.0: - resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} - engines: {node: '>=12.13.0'} + png-js@2.0.0: + resolution: {integrity: sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==} pngjs@7.0.0: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -13925,14 +14783,14 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.14: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.23: + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -13975,10 +14833,6 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - pretty-format@30.3.0: - resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -14012,12 +14866,12 @@ packages: prosemirror-gapcursor@1.4.1: resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} - prosemirror-highlight@0.15.1: - resolution: {integrity: sha512-KcJUGNgqLED+eK/cisNtY3M+eDNLkZyWCdyi7B3RoW3rKHnhkKawnJAcr9p1F/e3q+oDB5Y5OiIrC11bxP7tFA==} + prosemirror-highlight@0.15.3: + resolution: {integrity: sha512-WVV2st0fX1w2TkAgmTmdbj77BlWYuLfW4BGXPo8JfIWsSly5xYcfID8QJQL+GT8kisMRuu4jgT6JAqj1DOwvvg==} peerDependencies: '@lezer/common': ^1.0.0 '@lezer/highlight': ^1.0.0 - '@shikijs/types': ^1.29.2 || ^2.0.0 || ^3.0.0 || ^4.0.0 + '@shikijs/types': ^4.4.3 '@types/hast': ^3.0.0 highlight.js: ^11.9.0 lowlight: ^3.1.0 @@ -14172,9 +15026,6 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.2.4: resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} @@ -14412,15 +15263,13 @@ packages: rettime@0.7.0: resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown@1.0.0-rc.15: resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -14443,16 +15292,16 @@ packages: rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} - rrweb-cssom@0.7.1: - resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} - - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} run-applescript@7.1.0: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -14517,6 +15366,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + serve-handler@6.1.7: resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} @@ -14525,9 +15379,6 @@ packages: engines: {node: '>= 14'} hasBin: true - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} @@ -14546,9 +15397,14 @@ packages: setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.3: + resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -14562,11 +15418,8 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - shiki@3.23.0: - resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} - - shiki@4.0.2: - resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} + shiki@4.4.3: + resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} engines: {node: '>=20'} side-channel-list@1.0.1: @@ -14598,15 +15451,9 @@ packages: simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - simple-get@3.1.1: - resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} - simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-swizzle@0.2.4: - resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -14614,10 +15461,6 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - slate-history@0.110.3: resolution: {integrity: sha512-sgdff4Usdflmw5ZUbhDkxFwCBQ2qlDKMMkF93w66KdV48vHOgN2BmLrf+2H8SdX8PYIpP/cTB0w8qWC2GwhDVA==} peerDependencies: @@ -14671,6 +15514,10 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + speech-rule-engine@4.1.4: + resolution: {integrity: sha512-i/VCLG1fvRc95pMHRqG4aQNscv+9aIsqA2oI7ZQS51sTdUcDHYX6cpT8/tqZ+enjs1tKVwbRBWgxut9SWn+f9g==} + hasBin: true + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -14678,13 +15525,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - ssim.js@3.5.0: - resolution: {integrity: sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -14811,6 +15651,9 @@ packages: stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -14861,11 +15704,6 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - terser-webpack-plugin@5.5.0: resolution: {integrity: sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==} engines: {node: '>= 10.13.0'} @@ -14918,6 +15756,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinypool@2.1.0: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} @@ -14926,16 +15768,9 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - tldts-core@7.0.27: resolution: {integrity: sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==} - tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} - hasBin: true - tldts@7.0.27: resolution: {integrity: sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==} hasBin: true @@ -14944,10 +15779,6 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} - tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -14955,10 +15786,6 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} - tr46@6.0.0: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} @@ -14973,6 +15800,10 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + ts-morph@27.0.2: resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==} @@ -15046,6 +15877,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} @@ -15063,12 +15899,6 @@ packages: unconfig@7.5.0: resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} @@ -15157,6 +15987,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). @@ -15207,17 +16041,25 @@ packages: '@nuxt/kit': optional: true - vite-plus@0.1.24: - resolution: {integrity: sha512-b3fr6WtCiEhetjuzW/4KcEMOAMuZxoxZATWaXKmPzOLf1upG+pzKJOFZTb94D6wiPBlwcjxoaUtF7C3uAN+VjQ==} - engines: {node: ^20.19.0 || >=22.12.0} + vite-plus@0.2.9: + resolution: {integrity: sha512-8uRNqAxh9no3AU4Lep8BEYhkim07+3NO+mhuxTWiN0k30syGT/2+ue/DtWYhtzQ7yi2f2WKpjOhoI4/QkWWbUg==} + engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} hasBin: true + peerDependencies: + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + peerDependenciesMeta: + '@vitest/browser-playwright': + optional: true + '@vitest/browser-webdriverio': + optional: true vite@8.0.8: resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 + '@types/node': ^25.6.0 '@vitejs/devtools': ^0.1.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' @@ -15262,29 +16104,29 @@ packages: '@types/react-dom': ^18.0.0 || ^19.0.0 react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - vitest: 4.1.7 + vitest: 4.1.10 peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true - vitest@4.1.7: - resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.7 - '@vitest/browser-preview': 4.1.7 - '@vitest/browser-webdriverio': 4.1.7 - '@vitest/coverage-istanbul': 4.1.7 - '@vitest/coverage-v8': 4.1.7 - '@vitest/ui': 4.1.7 + '@types/node': ^25.6.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' - jsdom: '*' + jsdom: ^29.0.2 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': @@ -15333,10 +16175,6 @@ 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'} - webidl-conversions@8.0.1: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} @@ -15355,23 +16193,10 @@ packages: webpack-cli: optional: true - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} - whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} - whatwg-url@16.0.1: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -15408,8 +16233,8 @@ packages: engines: {node: '>=8'} hasBin: true - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + wicked-good-xpath@1.3.0: + resolution: {integrity: sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==} widest-line@4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} @@ -15550,9 +16375,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yaml@1.10.3: resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} engines: {node: '>= 6'} @@ -15593,12 +16415,21 @@ packages: yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + yuku-codegen@0.5.48: + resolution: {integrity: sha512-p7HxD5Xl4jzDzqMrGePAOeSHmRY4g58h4HuGq15weQFPxuPWd/W6e7nqp/+Lea6JfpOdBwJOAyXFqIZ/J9Zfnw==} + + yuku-parser@0.5.48: + resolution: {integrity: sha512-OWBfhrpgK9+/4+IXG9oT8Bao4AhViQA7vdyNNH7EUg8dQYgwa70XtIBWTpCEme1P1ECyoDNYkn0wT63f8XRcVA==} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} +ignoredOptionalDependencies: + - '@napi-rs/canvas' + snapshots: '@ai-sdk/anthropic@3.0.2(zod@4.3.6)': @@ -15667,6 +16498,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.7.0 + tinyexec: 1.2.4 + '@ariakit/core@0.4.18': {} '@ariakit/react-core@0.4.24(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': @@ -15683,14 +16519,6 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - '@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 - '@asamuzakjp/css-color@5.1.11': dependencies: '@asamuzakjp/generational-cache': 1.0.1 @@ -16291,19 +17119,6 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@base-ui/react@1.3.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@babel/runtime': 7.29.2 - '@base-ui/utils': 0.2.6(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@floating-ui/utils': 0.2.11 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - tabbable: 6.4.0 - use-sync-external-store: 1.6.0(react@19.2.5) - optionalDependencies: - '@types/react': 19.2.14 - '@base-ui/react@1.6.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 @@ -16318,17 +17133,6 @@ snapshots: '@types/react': 19.2.14 date-fns: 4.1.0 - '@base-ui/utils@0.2.6(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@babel/runtime': 7.29.2 - '@floating-ui/utils': 0.2.11 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - reselect: 5.1.1 - use-sync-external-store: 1.6.0(react@19.2.5) - optionalDependencies: - '@types/react': 19.2.14 - '@base-ui/utils@0.3.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 @@ -16365,31 +17169,23 @@ snapshots: '@better-fetch/fetch@1.1.21': {} + '@blazediff/core@1.9.1': {} + + '@braintree/sanitize-url@7.1.2': {} + '@bramus/specificity@2.4.2': dependencies: css-tree: 3.2.1 - '@csstools/color-helpers@5.1.0': {} + '@chevrotain/types@11.1.2': {} '@csstools/color-helpers@6.0.2': {} - '@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-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@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-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/color-helpers': 6.0.2 @@ -16397,10 +17193,6 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-tokenizer': 4.0.0 @@ -16409,8 +17201,6 @@ snapshots: optionalDependencies: css-tree: 3.2.1 - '@csstools/css-tokenizer@3.0.4': {} - '@csstools/css-tokenizer@4.0.0': {} '@date-fns/tz@1.4.1': {} @@ -16421,7 +17211,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.10.0': + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true @@ -16768,12 +17558,12 @@ snapshots: dependencies: '@formatjs/fast-memoize': 3.1.1 - '@fumadocs/base-ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)': + '@fumadocs/base-ui@16.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)': dependencies: - '@base-ui/react': 1.3.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@fumadocs/ui': 16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2) + '@base-ui/react': 1.6.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@fumadocs/ui': 16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2) class-variance-authority: 0.7.1 - fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) lucide-react: 0.563.0(react@19.2.5) next-themes: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 @@ -16782,12 +17572,15 @@ snapshots: scroll-into-view-if-needed: 3.1.0 optionalDependencies: '@types/react': 19.2.14 - next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tailwindcss: 4.2.2 + transitivePeerDependencies: + - '@date-fns/tz' + - date-fns - '@fumadocs/ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)': + '@fumadocs/ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)': dependencies: - fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) next-themes: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) postcss-selector-parser: 7.1.1 react: 19.2.5 @@ -16795,7 +17588,7 @@ snapshots: tailwind-merge: 3.5.0 optionalDependencies: '@types/react': 19.2.14 - next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tailwindcss: 4.2.2 '@handlewithcare/prosemirror-inputrules@0.1.4(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-view@1.42.2)': @@ -16849,119 +17642,122 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.4': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + '@img/colour@1.1.0': optional: true - '@img/sharp-darwin-arm64@0.34.5': + '@img/sharp-darwin-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.3.2 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.3.2 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-freebsd-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.2': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.3.2': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-linux-arm64@1.3.2': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm@1.3.2': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-ppc64@1.3.2': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-riscv64@1.3.2': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linux-s390x@1.3.2': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linux-x64@1.3.2': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-x64@1.3.2': optional: true - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-linux-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.3.2 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.3.2 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-ppc64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.3.2 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-riscv64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.3.2 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linux-s390x@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.3.2 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linux-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.3.2 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + '@img/sharp-linuxmusl-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 optional: true - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.35.3': dependencies: - '@emnapi/runtime': 1.10.0 + '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-webcontainers-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-arm64@0.35.3': optional: true - '@img/sharp-win32-x64@0.34.5': + '@img/sharp-win32-ia32@0.35.3': optional: true - '@inquirer/ansi@1.0.2': {} - - '@inquirer/confirm@5.1.21(@types/node@20.19.39)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@20.19.39) - '@inquirer/type': 3.0.10(@types/node@20.19.39) - optionalDependencies: - '@types/node': 20.19.39 + '@img/sharp-win32-x64@0.35.3': optional: true - '@inquirer/confirm@5.1.21(@types/node@22.13.13)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.13.13) - '@inquirer/type': 3.0.10(@types/node@22.13.13) - optionalDependencies: - '@types/node': 22.13.13 + '@inquirer/ansi@1.0.2': {} '@inquirer/confirm@5.1.21(@types/node@25.6.0)': dependencies: @@ -16969,34 +17765,6 @@ snapshots: '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: '@types/node': 25.6.0 - optional: true - - '@inquirer/core@10.3.2(@types/node@20.19.39)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@20.19.39) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 20.19.39 - optional: true - - '@inquirer/core@10.3.2(@types/node@22.13.13)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.13.13) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.13.13 '@inquirer/core@10.3.2(@types/node@25.6.0)': dependencies: @@ -17010,23 +17778,12 @@ snapshots: yoctocolors-cjs: 2.1.3 optionalDependencies: '@types/node': 25.6.0 - optional: true '@inquirer/figures@1.0.15': {} - '@inquirer/type@3.0.10(@types/node@20.19.39)': - optionalDependencies: - '@types/node': 20.19.39 - optional: true - - '@inquirer/type@3.0.10(@types/node@22.13.13)': - optionalDependencies: - '@types/node': 22.13.13 - '@inquirer/type@3.0.10(@types/node@25.6.0)': optionalDependencies: '@types/node': 25.6.0 - optional: true '@isaacs/cliui@8.0.2': dependencies: @@ -17037,33 +17794,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jest/diff-sequences@30.3.0': {} - - '@jest/expect-utils@30.3.0': - dependencies: - '@jest/get-type': 30.1.0 - - '@jest/get-type@30.1.0': {} - - '@jest/pattern@30.0.1': - dependencies: - '@types/node': 22.13.13 - jest-regex-util: 30.0.1 - - '@jest/schemas@30.0.5': - dependencies: - '@sinclair/typebox': 0.34.49 - - '@jest/types@30.3.0': - dependencies: - '@jest/pattern': 30.0.1 - '@jest/schemas': 30.0.5 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 22.13.13 - '@types/yargs': 17.0.35 - chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -17216,22 +17946,6 @@ snapshots: dependencies: react: 19.2.5 - '@mapbox/node-pre-gyp@1.0.11': - dependencies: - detect-libc: 2.1.2 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.7.4 - tar: 6.2.1 - transitivePeerDependencies: - - encoding - - supports-color - optional: true - '@marsidev/react-turnstile@1.5.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: react: 19.2.5 @@ -17241,7 +17955,7 @@ snapshots: dependencies: '@types/estree': 1.0.8 '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdx': 2.0.13 acorn: 8.16.0 collapse-white-space: 2.1.0 @@ -17267,6 +17981,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@mermaid-js/parser@1.2.0': + dependencies: + '@chevrotain/types': 11.1.2 + '@mswjs/interceptors@0.37.6': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -17376,32 +18094,34 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.2.7': {} + '@next/env@16.3.0': {} - '@next/swc-darwin-arm64@16.2.7': + '@next/swc-darwin-arm64@16.3.0': optional: true - '@next/swc-darwin-x64@16.2.7': + '@next/swc-darwin-x64@16.3.0': optional: true - '@next/swc-linux-arm64-gnu@16.2.7': + '@next/swc-linux-arm64-gnu@16.3.0': optional: true - '@next/swc-linux-arm64-musl@16.2.7': + '@next/swc-linux-arm64-musl@16.3.0': optional: true - '@next/swc-linux-x64-gnu@16.2.7': + '@next/swc-linux-x64-gnu@16.3.0': optional: true - '@next/swc-linux-x64-musl@16.2.7': + '@next/swc-linux-x64-musl@16.3.0': optional: true - '@next/swc-win32-arm64-msvc@16.2.7': + '@next/swc-win32-arm64-msvc@16.3.0': optional: true - '@next/swc-win32-x64-msvc@16.2.7': + '@next/swc-win32-x64-msvc@16.3.0': optional: true + '@noble/ciphers@1.3.0': {} + '@noble/ciphers@2.1.1': {} '@noble/hashes@1.8.0': {} @@ -17684,145 +18404,145 @@ snapshots: '@orama/orama@3.1.18': {} - '@oxc-project/runtime@0.133.0': {} + '@oxc-project/runtime@0.143.0': {} '@oxc-project/types@0.124.0': {} - '@oxc-project/types@0.133.0': {} + '@oxc-project/types@0.143.0': {} - '@oxfmt/binding-android-arm-eabi@0.52.0': + '@oxfmt/binding-android-arm-eabi@0.62.0': optional: true - '@oxfmt/binding-android-arm64@0.52.0': + '@oxfmt/binding-android-arm64@0.62.0': optional: true - '@oxfmt/binding-darwin-arm64@0.52.0': + '@oxfmt/binding-darwin-arm64@0.62.0': optional: true - '@oxfmt/binding-darwin-x64@0.52.0': + '@oxfmt/binding-darwin-x64@0.62.0': optional: true - '@oxfmt/binding-freebsd-x64@0.52.0': + '@oxfmt/binding-freebsd-x64@0.62.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.52.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.62.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.52.0': + '@oxfmt/binding-linux-arm-musleabihf@0.62.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.52.0': + '@oxfmt/binding-linux-arm64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.52.0': + '@oxfmt/binding-linux-arm64-musl@0.62.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.52.0': + '@oxfmt/binding-linux-ppc64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.52.0': + '@oxfmt/binding-linux-riscv64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.52.0': + '@oxfmt/binding-linux-riscv64-musl@0.62.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.52.0': + '@oxfmt/binding-linux-s390x-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.52.0': + '@oxfmt/binding-linux-x64-gnu@0.62.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.52.0': + '@oxfmt/binding-linux-x64-musl@0.62.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.52.0': + '@oxfmt/binding-openharmony-arm64@0.62.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.52.0': + '@oxfmt/binding-win32-arm64-msvc@0.62.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.52.0': + '@oxfmt/binding-win32-ia32-msvc@0.62.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.52.0': + '@oxfmt/binding-win32-x64-msvc@0.62.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.23.0': + '@oxlint-tsgolint/darwin-arm64@7.0.2001': optional: true - '@oxlint-tsgolint/darwin-x64@0.23.0': + '@oxlint-tsgolint/darwin-x64@7.0.2001': optional: true - '@oxlint-tsgolint/linux-arm64@0.23.0': + '@oxlint-tsgolint/linux-arm64@7.0.2001': optional: true - '@oxlint-tsgolint/linux-x64@0.23.0': + '@oxlint-tsgolint/linux-x64@7.0.2001': optional: true - '@oxlint-tsgolint/win32-arm64@0.23.0': + '@oxlint-tsgolint/win32-arm64@7.0.2001': optional: true - '@oxlint-tsgolint/win32-x64@0.23.0': + '@oxlint-tsgolint/win32-x64@7.0.2001': optional: true - '@oxlint/binding-android-arm-eabi@1.67.0': + '@oxlint/binding-android-arm-eabi@1.77.0': optional: true - '@oxlint/binding-android-arm64@1.67.0': + '@oxlint/binding-android-arm64@1.77.0': optional: true - '@oxlint/binding-darwin-arm64@1.67.0': + '@oxlint/binding-darwin-arm64@1.77.0': optional: true - '@oxlint/binding-darwin-x64@1.67.0': + '@oxlint/binding-darwin-x64@1.77.0': optional: true - '@oxlint/binding-freebsd-x64@1.67.0': + '@oxlint/binding-freebsd-x64@1.77.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.67.0': + '@oxlint/binding-linux-arm-gnueabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.67.0': + '@oxlint/binding-linux-arm-musleabihf@1.77.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.67.0': + '@oxlint/binding-linux-arm64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.67.0': + '@oxlint/binding-linux-arm64-musl@1.77.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.67.0': + '@oxlint/binding-linux-ppc64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.67.0': + '@oxlint/binding-linux-riscv64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.67.0': + '@oxlint/binding-linux-riscv64-musl@1.77.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.67.0': + '@oxlint/binding-linux-s390x-gnu@1.77.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.67.0': + '@oxlint/binding-linux-x64-gnu@1.77.0': optional: true - '@oxlint/binding-linux-x64-musl@1.67.0': + '@oxlint/binding-linux-x64-musl@1.77.0': optional: true - '@oxlint/binding-openharmony-arm64@1.67.0': + '@oxlint/binding-openharmony-arm64@1.77.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.67.0': + '@oxlint/binding-win32-arm64-msvc@1.77.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.67.0': + '@oxlint/binding-win32-ia32-msvc@1.77.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.67.0': + '@oxlint/binding-win32-x64-msvc@1.77.0': optional: true - '@oxlint/plugins@1.61.0': {} + '@oxlint/plugins@1.73.0': {} '@pkgjs/parseargs@0.11.0': optional: true @@ -17831,11 +18551,11 @@ snapshots: dependencies: playwright: 1.60.0 - '@polar-sh/better-auth@1.8.3(@polar-sh/sdk@0.42.5)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@stripe/stripe-js@7.9.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(better-auth@1.4.22(better-sqlite3@12.8.0)(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))))(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5)(redux@5.0.1)(zod@4.3.6)': + '@polar-sh/better-auth@1.8.3(@polar-sh/sdk@0.42.5)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@stripe/stripe-js@7.9.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(better-auth@1.4.22(better-sqlite3@12.8.0)(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.10))(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5)(redux@5.0.1)(zod@4.3.6)': dependencies: '@polar-sh/checkout': 0.2.0(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@stripe/stripe-js@7.9.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5)(redux@5.0.1) '@polar-sh/sdk': 0.42.5 - better-auth: 1.4.22(better-sqlite3@12.8.0)(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))) + better-auth: 1.4.22(better-sqlite3@12.8.0)(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.10) zod: 4.3.6 transitivePeerDependencies: - '@stripe/react-stripe-js' @@ -17878,7 +18598,7 @@ snapshots: '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@radix-ui/react-label': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@radix-ui/react-separator': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -17929,6 +18649,8 @@ snapshots: '@radix-ui/primitive@1.1.3': {} + '@radix-ui/primitive@1.1.7': {} + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -17969,6 +18691,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -18050,6 +18781,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -18070,6 +18807,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-context@1.2.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -18111,6 +18854,19 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -18132,6 +18888,23 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) @@ -18181,6 +18954,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-id@1.1.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -18324,6 +19104,29 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.14)(react@19.2.5) + aria-hidden: 1.2.6 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -18342,6 +19145,34 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/rect': 1.1.3 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -18352,6 +19183,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) @@ -18362,6 +19202,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) @@ -18522,6 +19371,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-slot@1.3.3(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -18640,6 +19496,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)': dependencies: '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5) @@ -18648,6 +19510,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) @@ -18655,6 +19526,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) @@ -18675,6 +19553,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: react: 19.2.5 @@ -18688,6 +19572,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/rect': 1.1.3 + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) @@ -18695,6 +19586,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-use-size@1.1.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -18706,6 +19604,8 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@radix-ui/rect@1.1.3': {} + '@react-email/body@0.3.0(react@19.2.5)': dependencies: react: 19.2.5 @@ -18839,48 +19739,54 @@ snapshots: dependencies: react: 19.2.5 - '@react-pdf/fns@3.1.2': {} + '@react-pdf/fns@3.1.3': {} - '@react-pdf/font@4.0.4': + '@react-pdf/font@4.0.8': dependencies: - '@react-pdf/pdfkit': 4.1.0 - '@react-pdf/types': 2.9.2 + '@react-pdf/pdfkit': 5.1.1 + '@react-pdf/types': 2.11.1 fontkit: 2.0.4 is-url: 1.2.4 - '@react-pdf/image@3.0.4': + '@react-pdf/image@3.1.0': dependencies: - '@react-pdf/png-js': 3.0.0 + '@react-pdf/svg': 1.1.0 jay-peg: 1.1.1 + png-js: 2.0.0 - '@react-pdf/layout@4.4.2': + '@react-pdf/layout@4.6.1': dependencies: - '@react-pdf/fns': 3.1.2 - '@react-pdf/image': 3.0.4 - '@react-pdf/primitives': 4.1.1 - '@react-pdf/stylesheet': 6.1.2 - '@react-pdf/textkit': 6.1.0 - '@react-pdf/types': 2.9.2 + '@react-pdf/fns': 3.1.3 + '@react-pdf/image': 3.1.0 + '@react-pdf/primitives': 4.3.0 + '@react-pdf/stylesheet': 6.2.1 + '@react-pdf/textkit': 6.3.0 + '@react-pdf/types': 2.11.1 emoji-regex-xs: 1.0.0 queue: 6.0.2 yoga-layout: 3.2.1 - '@react-pdf/pdfkit@4.1.0': + '@react-pdf/math@2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5)': + dependencies: + '@react-pdf/renderer': 4.5.1(react@19.2.5) + '@react-pdf/svg': 1.1.0 + mathjax-full: 3.2.2 + react: 19.2.5 + + '@react-pdf/pdfkit@5.1.1': dependencies: '@babel/runtime': 7.29.2 - '@react-pdf/png-js': 3.0.0 + '@noble/ciphers': 1.3.0 + '@noble/hashes': 1.8.0 browserify-zlib: 0.2.0 - crypto-js: 4.2.0 fontkit: 2.0.4 jay-peg: 1.1.1 + js-md5: 0.8.3 linebreak: 1.1.0 + png-js: 2.0.0 vite-compatible-readable-stream: 3.6.1 - '@react-pdf/png-js@3.0.0': - dependencies: - browserify-zlib: 0.2.0 - - '@react-pdf/primitives@4.1.1': {} + '@react-pdf/primitives@4.3.0': {} '@react-pdf/reconciler@2.0.0(react@19.2.5)': dependencies: @@ -18888,57 +19794,61 @@ snapshots: react: 19.2.5 scheduler: 0.25.0-rc-603e6108-20241029 - '@react-pdf/render@4.3.2': + '@react-pdf/render@4.5.1': dependencies: '@babel/runtime': 7.29.2 - '@react-pdf/fns': 3.1.2 - '@react-pdf/primitives': 4.1.1 - '@react-pdf/textkit': 6.1.0 - '@react-pdf/types': 2.9.2 + '@react-pdf/fns': 3.1.3 + '@react-pdf/primitives': 4.3.0 + '@react-pdf/textkit': 6.3.0 + '@react-pdf/types': 2.11.1 abs-svg-path: 0.1.1 - color-string: 1.9.1 + color-string: 2.1.4 normalize-svg-path: 1.1.0 parse-svg-path: 0.1.2 svg-arc-to-cubic-bezier: 3.2.0 - '@react-pdf/renderer@4.3.2(react@19.2.5)': + '@react-pdf/renderer@4.5.1(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 - '@react-pdf/fns': 3.1.2 - '@react-pdf/font': 4.0.4 - '@react-pdf/layout': 4.4.2 - '@react-pdf/pdfkit': 4.1.0 - '@react-pdf/primitives': 4.1.1 + '@react-pdf/fns': 3.1.3 + '@react-pdf/font': 4.0.8 + '@react-pdf/layout': 4.6.1 + '@react-pdf/pdfkit': 5.1.1 + '@react-pdf/primitives': 4.3.0 '@react-pdf/reconciler': 2.0.0(react@19.2.5) - '@react-pdf/render': 4.3.2 - '@react-pdf/types': 2.9.2 + '@react-pdf/render': 4.5.1 + '@react-pdf/types': 2.11.1 events: 3.3.0 object-assign: 4.1.1 prop-types: 15.8.1 queue: 6.0.2 react: 19.2.5 - '@react-pdf/stylesheet@6.1.2': + '@react-pdf/stylesheet@6.2.1': dependencies: - '@react-pdf/fns': 3.1.2 - '@react-pdf/types': 2.9.2 - color-string: 1.9.1 + '@react-pdf/fns': 3.1.3 + '@react-pdf/types': 2.11.1 + color-string: 2.1.4 hsl-to-hex: 1.0.0 media-engine: 1.0.3 postcss-value-parser: 4.2.0 - '@react-pdf/textkit@6.1.0': + '@react-pdf/svg@1.1.0': + dependencies: + '@react-pdf/primitives': 4.3.0 + + '@react-pdf/textkit@6.3.0': dependencies: - '@react-pdf/fns': 3.1.2 + '@react-pdf/fns': 3.1.3 bidi-js: 1.0.3 hyphen: 1.14.1 unicode-properties: 1.4.1 - '@react-pdf/types@2.9.2': + '@react-pdf/types@2.11.1': dependencies: - '@react-pdf/font': 4.0.4 - '@react-pdf/primitives': 4.1.1 - '@react-pdf/stylesheet': 6.1.2 + '@react-pdf/font': 4.0.8 + '@react-pdf/primitives': 4.3.0 + '@react-pdf/stylesheet': 6.2.1 '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1))(react@19.2.5)': dependencies: @@ -18947,7 +19857,7 @@ snapshots: immer: 11.1.4 redux: 5.0.1 redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.1.1 + reselect: 5.2.0 optionalDependencies: react: 19.2.5 react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1) @@ -19196,7 +20106,7 @@ snapshots: '@sentry/core@10.47.0': {} - '@sentry/nextjs@10.47.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(webpack@5.105.4(esbuild@0.27.5))': + '@sentry/nextjs@10.47.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(webpack@5.105.4(esbuild@0.27.5))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 @@ -19209,7 +20119,7 @@ snapshots: '@sentry/react': 10.47.0(react@19.2.5) '@sentry/vercel-edge': 10.47.0 '@sentry/webpack-plugin': 5.1.1(webpack@5.105.4(esbuild@0.27.5)) - next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) rollup: 4.60.1 stacktrace-parser: 0.1.11 transitivePeerDependencies: @@ -19309,105 +20219,93 @@ snapshots: '@shikijs/core@3.23.0': dependencies: - '@shikijs/types': 3.23.0 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 hast-util-to-html: 9.0.5 '@shikijs/core@4.0.2': dependencies: '@shikijs/primitive': 4.0.2 - '@shikijs/types': 4.0.2 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.5 - - '@shikijs/engine-javascript@4.0.2': + '@shikijs/core@4.4.3': dependencies: - '@shikijs/types': 4.0.2 + '@shikijs/primitive': 4.4.3 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.5 + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 - '@shikijs/engine-oniguruma@3.23.0': + '@shikijs/engine-javascript@4.4.3': dependencies: - '@shikijs/types': 3.23.0 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 - '@shikijs/engine-oniguruma@4.0.2': + '@shikijs/engine-oniguruma@4.4.3': dependencies: - '@shikijs/types': 4.0.2 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs-precompiled@4.0.2': + '@shikijs/langs-precompiled@4.4.3': dependencies: - '@shikijs/types': 4.0.2 - oniguruma-to-es: 4.3.5 + '@shikijs/types': 4.4.3 + oniguruma-to-es: 4.3.6 - '@shikijs/langs@3.23.0': + '@shikijs/langs@4.4.3': dependencies: - '@shikijs/types': 3.23.0 + '@shikijs/types': 4.4.3 - '@shikijs/langs@4.0.2': + '@shikijs/primitive@4.0.2': dependencies: - '@shikijs/types': 4.0.2 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 - '@shikijs/primitive@4.0.2': + '@shikijs/primitive@4.4.3': dependencies: - '@shikijs/types': 4.0.2 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 - '@shikijs/rehype@3.23.0': + '@shikijs/rehype@4.4.3': dependencies: - '@shikijs/types': 3.23.0 - '@types/hast': 3.0.4 + '@shikijs/types': 4.4.3 + '@types/hast': 3.0.5 hast-util-to-string: 3.0.1 - shiki: 3.23.0 + shiki: 4.4.3 unified: 11.0.5 unist-util-visit: 5.1.0 - '@shikijs/themes@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - - '@shikijs/themes@4.0.2': + '@shikijs/themes@4.4.3': dependencies: - '@shikijs/types': 4.0.2 + '@shikijs/types': 4.4.3 '@shikijs/transformers@3.23.0': dependencies: '@shikijs/core': 3.23.0 - '@shikijs/types': 3.23.0 + '@shikijs/types': 4.4.3 - '@shikijs/twoslash@4.0.2(typescript@5.9.3)': + '@shikijs/twoslash@4.0.2(typescript@7.0.2)': dependencies: '@shikijs/core': 4.0.2 - '@shikijs/types': 4.0.2 - twoslash: 0.3.6(typescript@5.9.3) - typescript: 5.9.3 + '@shikijs/types': 4.4.3 + twoslash: 0.3.6(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - supports-color - '@shikijs/types@3.23.0': + '@shikijs/types@4.4.3': dependencies: '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - '@shikijs/types@4.0.2': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@shikijs/vscode-textmate@10.0.2': {} - '@sinclair/typebox@0.34.49': {} - '@smithy/chunked-blob-reader-native@4.2.3': dependencies: '@smithy/util-base64': 4.3.2 @@ -19836,20 +20734,6 @@ snapshots: postcss: 8.5.14 tailwindcss: 4.2.2 - '@tailwindcss/vite@4.2.2(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - tailwindcss: 4.2.2 - vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - - '@tailwindcss/vite@4.2.2(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - tailwindcss: 4.2.2 - vite: 8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - '@tailwindcss/vite@4.2.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.2.2 @@ -19886,6 +20770,10 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@testing-library/user-event@14.6.3(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@tiptap/core@3.29.2(@tiptap/pm@3.29.2)': dependencies: '@tiptap/pm': 3.29.2 @@ -19977,7 +20865,7 @@ snapshots: dependencies: minimatch: 10.2.5 path-browserify: 1.0.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 '@tybys/wasm-util@0.10.1': dependencies: @@ -19988,7 +20876,7 @@ snapshots: '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 '@types/chai@5.2.3': dependencies: @@ -19997,47 +20885,140 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 '@types/cors@2.8.19': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 '@types/d3-array@3.2.2': {} + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + '@types/d3-color@3.1.3': {} + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + '@types/d3-ease@3.0.2': {} + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 '@types/d3-path@3.1.1': {} + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + '@types/d3-scale@4.0.9': dependencies: '@types/d3-time': 3.0.4 + '@types/d3-selection@3.0.11': {} + '@types/d3-shape@3.1.8': dependencies: '@types/d3-path': 3.1.1 + '@types/d3-time-format@4.0.3': {} + '@types/d3-time@3.0.4': {} '@types/d3-timer@3.0.2': {} - '@types/debug@4.1.13': + '@types/d3-transition@3.0.9': dependencies: - '@types/ms': 2.1.0 - - '@types/deep-eql@4.0.2': {} + '@types/d3-selection': 3.0.11 - '@types/diff@6.0.0': {} + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 - '@types/eslint-scope@3.7.7': + '@types/d3@7.4.3': dependencies: - '@types/eslint': 9.6.1 + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/diff@6.0.0': {} + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 '@types/estree': 1.0.8 '@types/eslint@9.6.1': @@ -20051,34 +21032,15 @@ snapshots: '@types/estree@1.0.8': {} - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 + '@types/geojson@7946.0.16': {} - '@types/istanbul-lib-coverage@2.0.6': {} - - '@types/istanbul-lib-report@3.0.3': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - - '@types/istanbul-reports@3.0.4': + '@types/hast@3.0.5': dependencies: - '@types/istanbul-lib-report': 3.0.3 - - '@types/jest-image-snapshot@6.4.1': - dependencies: - '@types/jest': 30.0.0 - '@types/pixelmatch': 5.2.6 - ssim.js: 3.5.0 - - '@types/jest@30.0.0': - dependencies: - expect: 30.3.0 - pretty-format: 30.3.0 + '@types/unist': 3.0.3 '@types/jsdom@21.1.7': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -20088,6 +21050,8 @@ snapshots: '@types/json5@0.0.29': {} + '@types/katex@0.16.8': {} + '@types/lodash.foreach@4.5.9': dependencies: '@types/lodash': 4.17.24 @@ -20116,15 +21080,7 @@ snapshots: '@types/mysql@2.15.27': dependencies: - '@types/node': 22.13.13 - - '@types/node@20.19.39': - dependencies: - undici-types: 6.21.0 - - '@types/node@22.13.13': - dependencies: - undici-types: 6.20.0 + '@types/node': 25.6.0 '@types/node@25.6.0': dependencies: @@ -20132,7 +21088,7 @@ snapshots: '@types/nodemailer@7.0.11': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 '@types/parse-json@4.0.2': {} @@ -20142,20 +21098,16 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 pg-protocol: 1.13.0 pg-types: 2.2.0 '@types/pg@8.20.0': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 pg-protocol: 1.13.0 pg-types: 2.2.0 - '@types/pixelmatch@5.2.6': - dependencies: - '@types/node': 22.13.13 - '@types/prop-types@15.7.15': {} '@types/react-dom@19.2.3(@types/react@19.2.14)': @@ -20172,16 +21124,17 @@ snapshots: '@types/retry@0.12.2': {} - '@types/stack-utils@2.0.3': {} - '@types/statuses@2.0.6': {} '@types/tedious@4.0.14': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 '@types/tough-cookie@4.0.5': {} + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -20193,49 +21146,72 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 - '@types/yargs-parser@21.0.3': {} + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true - '@types/yargs@17.0.35': - dependencies: - '@types/yargs-parser': 21.0.3 + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260615.1': + '@typescript/typescript-darwin-x64@7.0.2': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260615.1': + '@typescript/typescript-freebsd-arm64@7.0.2': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260615.1': + '@typescript/typescript-freebsd-x64@7.0.2': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260615.1': + '@typescript/typescript-linux-arm64@7.0.2': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260615.1': + '@typescript/typescript-linux-arm@7.0.2': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260615.1': + '@typescript/typescript-linux-loong64@7.0.2': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260615.1': + '@typescript/typescript-linux-mips64el@7.0.2': optional: true - '@typescript/native-preview@7.0.0-dev.20260615.1': - optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260615.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260615.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260615.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260615.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260615.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260615.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260615.1 + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true - '@typescript/vfs@1.6.4(typescript@5.9.3)': + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@typescript/vfs@1.6.4(typescript@7.0.2)': dependencies: debug: 4.4.3 - typescript: 5.9.3 + typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -20368,42 +21344,40 @@ snapshots: '@uppy/core': 3.13.1 '@uppy/utils': 5.9.0 - '@vercel/analytics@1.6.1(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)': + '@upsetjs/venn.js@2.0.0': optionalDependencies: - next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + '@vercel/analytics@1.6.1(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)': + optionalDependencies: + next: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 '@vercel/oidc@3.0.5': {} - '@vitejs/devtools-kit@0.1.13(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0)': + '@vitejs/devtools-kit@0.1.13(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0)': dependencies: - '@vitejs/devtools-rpc': 0.1.13(typescript@5.9.3)(ws@8.20.0) + '@vitejs/devtools-rpc': 0.1.13(typescript@7.0.2)(ws@8.20.0) birpc: 4.0.0 ohash: 2.0.11 - vite: 8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - typescript - ws - '@vitejs/devtools-rpc@0.1.13(typescript@5.9.3)(ws@8.20.0)': + '@vitejs/devtools-rpc@0.1.13(typescript@7.0.2)(ws@8.20.0)': dependencies: birpc: 4.0.0 ohash: 2.0.11 p-limit: 7.3.0 structured-clone-es: 2.0.0 - valibot: 1.3.1(typescript@5.9.3) + valibot: 1.3.1(typescript@7.0.2) optionalDependencies: ws: 8.20.0 transitivePeerDependencies: - typescript - '@vitejs/plugin-react@6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - optionalDependencies: - babel-plugin-react-compiler: 1.0.0 - '@vitejs/plugin-react@6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 @@ -20411,402 +21385,159 @@ snapshots: optionalDependencies: babel-plugin-react-compiler: 1.0.0 - '@vitest/expect@4.1.7': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 - chai: 6.2.2 - tinyrainbow: 3.1.0 - - '@vitest/mocker@4.1.7(msw@2.11.5(@types/node@20.19.39)(typescript@5.9.3))(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.7 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.11.5(@types/node@20.19.39)(typescript@5.9.3) - vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - - '@vitest/mocker@4.1.7(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.7 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.11.5(@types/node@25.6.0)(typescript@5.9.3) - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - optional: true - - '@vitest/pretty-format@4.1.5': + '@vitest/browser-playwright@4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(playwright@1.60.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: + '@vitest/browser': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/mocker': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + playwright: 1.60.0 tinyrainbow: 3.1.0 - - '@vitest/pretty-format@4.1.7': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@4.1.7': - dependencies: - '@vitest/utils': 4.1.7 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.7': - dependencies: - '@vitest/pretty-format': 4.1.7 - '@vitest/utils': 4.1.7 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.7': {} - - '@vitest/ui@4.1.5(vitest@4.1.7)': - dependencies: - '@vitest/utils': 4.1.5 - fflate: 0.8.3 - flatted: 3.4.2 - pathe: 2.0.3 - sirv: 3.0.2 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@20.19.39)(typescript@5.9.3))(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) - - '@vitest/utils@4.1.5': - dependencies: - '@vitest/pretty-format': 4.1.5 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - '@vitest/utils@4.1.7': - dependencies: - '@vitest/pretty-format': 4.1.7 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - '@voidzero-dev/vite-plus-core@0.1.24(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)': - dependencies: - '@oxc-project/runtime': 0.133.0 - '@oxc-project/types': 0.133.0 - lightningcss: 1.32.0 - postcss: 8.5.14 - optionalDependencies: - '@types/node': 20.19.39 - esbuild: 0.27.5 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.46.2 - tsx: 4.21.0 - typescript: 5.9.3 - yaml: 2.9.0 - - '@voidzero-dev/vite-plus-core@0.1.24(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)': - dependencies: - '@oxc-project/runtime': 0.133.0 - '@oxc-project/types': 0.133.0 - lightningcss: 1.32.0 - postcss: 8.5.14 - optionalDependencies: - '@types/node': 22.13.13 - esbuild: 0.27.5 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.46.2 - tsx: 4.21.0 - typescript: 5.9.3 - yaml: 2.9.0 - - '@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)': - dependencies: - '@oxc-project/runtime': 0.133.0 - '@oxc-project/types': 0.133.0 - lightningcss: 1.32.0 - postcss: 8.5.14 - optionalDependencies: - '@types/node': 25.6.0 - esbuild: 0.27.5 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.46.2 - tsx: 4.21.0 - typescript: 5.9.3 - yaml: 2.9.0 - - '@voidzero-dev/vite-plus-darwin-arm64@0.1.24': - optional: true - - '@voidzero-dev/vite-plus-darwin-x64@0.1.24': - optional: true - - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.24': - optional: true - - '@voidzero-dev/vite-plus-linux-arm64-musl@0.1.24': - optional: true - - '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.24': - optional: true - - '@voidzero-dev/vite-plus-linux-x64-musl@0.1.24': - optional: true - - '@voidzero-dev/vite-plus-test@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - es-module-lexer: 1.7.0 - obug: 2.1.1 - pixelmatch: 7.2.0 - pngjs: 7.0.0 - sirv: 3.0.2 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - ws: 8.20.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 20.19.39 - '@vitest/ui': 4.1.5(vitest@4.1.7) - jsdom: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@tsdown/css' - - '@tsdown/exe' - - '@vitejs/devtools' - bufferutil - - esbuild - - jiti - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - typescript - - unplugin-unused - - unrun + - msw - utf-8-validate - - yaml + - vite - '@voidzero-dev/vite-plus-test@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)': + '@vitest/browser-preview@4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - es-module-lexer: 1.7.0 - obug: 2.1.1 - pixelmatch: 7.2.0 - pngjs: 7.0.0 - sirv: 3.0.2 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - ws: 8.20.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 20.19.39 - jsdom: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) + '@testing-library/dom': 10.4.1 + '@testing-library/user-event': 14.6.3(@testing-library/dom@10.4.1) + '@vitest/browser': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@tsdown/css' - - '@tsdown/exe' - - '@vitejs/devtools' - bufferutil - - esbuild - - jiti - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - typescript - - unplugin-unused - - unrun + - msw - utf-8-validate - - yaml + - vite - '@voidzero-dev/vite-plus-test@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)': + '@vitest/browser@4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - es-module-lexer: 1.7.0 - obug: 2.1.1 - pixelmatch: 7.2.0 + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - vite: 8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + tinyrainbow: 3.1.0 + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) ws: 8.20.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 22.13.13 - jsdom: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@tsdown/css' - - '@tsdown/exe' - - '@vitejs/devtools' - bufferutil - - esbuild - - jiti - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - typescript - - unplugin-unused - - unrun + - msw - utf-8-validate - - yaml + - vite - '@voidzero-dev/vite-plus-test@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)': + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - es-module-lexer: 1.7.0 - obug: 2.1.1 - pixelmatch: 7.2.0 - pngjs: 7.0.0 - sirv: 3.0.2 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - ws: 8.20.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 22.13.13 - jsdom: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@tsdown/css' - - '@tsdown/exe' - - '@vitejs/devtools' - - bufferutil - - esbuild - - jiti - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - yaml + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-test@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)': + '@vitest/mocker@4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - es-module-lexer: 1.7.0 - obug: 2.1.1 - pixelmatch: 7.2.0 - pngjs: 7.0.0 - sirv: 3.0.2 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - ws: 8.20.0 + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 25.6.0 - jsdom: 25.0.1(canvas@2.11.2) - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@tsdown/css' - - '@tsdown/exe' - - '@vitejs/devtools' - - bufferutil - - esbuild - - jiti - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - yaml + msw: 2.11.5(@types/node@25.6.0)(typescript@7.0.2) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/pretty-format@4.1.5': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} - '@voidzero-dev/vite-plus-test@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)': + '@vitest/ui@4.1.5(vitest@4.1.10)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - es-module-lexer: 1.7.0 - obug: 2.1.1 - pixelmatch: 7.2.0 - pngjs: 7.0.0 + '@vitest/utils': 4.1.5 + fflate: 0.8.3 + flatted: 3.4.2 + pathe: 2.0.3 sirv: 3.0.2 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - ws: 8.20.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@voidzero-dev/vite-plus-core@0.2.9(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(yaml@2.9.0)': + dependencies: + '@oxc-project/runtime': 0.143.0 + '@oxc-project/types': 0.143.0 + lightningcss: 1.33.0 + postcss: 8.5.14 + yuku-codegen: 0.5.48 + yuku-parser: 0.5.48 optionalDependencies: - '@opentelemetry/api': 1.9.1 '@types/node': 25.6.0 - jsdom: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@tsdown/css' - - '@tsdown/exe' - - '@vitejs/devtools' - - bufferutil - - esbuild - - jiti - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - yaml + '@voidzero-dev/vite-plus-darwin-arm64': 0.2.9 + '@voidzero-dev/vite-plus-darwin-x64': 0.2.9 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.9 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.9 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.9 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.9 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.9 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.9 + esbuild: 0.27.5 + fsevents: 2.3.3 + jiti: 2.6.1 + terser: 5.46.2 + tsx: 4.21.0 + typescript: 7.0.2 + yaml: 2.9.0 + + '@voidzero-dev/vite-plus-darwin-arm64@0.2.9': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@0.2.9': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.9': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.9': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.9': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-musl@0.2.9': + optional: true - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.24': + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.9': optional: true - '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.24': + '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.9': optional: true '@webassemblyjs/ast@1.14.1': @@ -20885,6 +21616,8 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@xmldom/xmldom@0.9.10': {} + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} @@ -20905,7 +21638,7 @@ snapshots: '@y-sweet/sdk@0.6.4': dependencies: - '@types/node': 20.19.39 + '@types/node': 25.6.0 '@y/prosemirror@2.0.0-6(patch_hash=e49b17b47e301dd138d7e383a779a0e2125bf7f038e10e3c740e26b43d988776)(@y/protocols@1.0.6-rc.1(@y/y@14.0.0-rc.23))(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-view@1.42.2)': dependencies: @@ -20931,13 +21664,78 @@ snapshots: dependencies: lib0: 1.0.0-rc.22 - '@zeit/schemas@2.36.0': {} + '@yuku-codegen/binding-darwin-arm64@0.5.48': + optional: true - '@zip.js/zip.js@2.8.26': {} + '@yuku-codegen/binding-darwin-x64@0.5.48': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.5.48': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.5.48': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.5.48': + optional: true + + '@yuku-codegen/binding-win32-x64@0.5.48': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.5.48': + optional: true + + '@yuku-parser/binding-darwin-x64@0.5.48': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.5.48': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.5.48': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.5.48': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.5.48': + optional: true + + '@yuku-parser/binding-win32-arm64@0.5.48': + optional: true - abbrev@1.1.1: + '@yuku-parser/binding-win32-x64@0.5.48': optional: true + '@yuku-toolchain/types@0.5.43': {} + + '@zeit/schemas@2.36.0': {} + + '@zip.js/zip.js@2.8.26': {} + abs-svg-path@0.1.1: {} abstract-leveldown@6.2.3: @@ -20983,8 +21781,6 @@ snapshots: transitivePeerDependencies: - supports-color - agent-base@7.1.4: {} - ai@6.0.5(zod@4.3.6): dependencies: '@ai-sdk/gateway': 3.0.4(zod@4.3.6) @@ -21045,17 +21841,8 @@ snapshots: ansis@4.2.0: {} - aproba@2.1.0: - optional: true - arch@2.2.0: {} - are-we-there-yet@2.0.0: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - optional: true - arg@5.0.2: {} argparse@1.0.10: @@ -21186,7 +21973,7 @@ snapshots: baseline-browser-mapping@2.10.17: {} - better-auth@1.4.22(better-sqlite3@12.8.0)(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))): + better-auth@1.4.22(better-sqlite3@12.8.0)(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(pg@8.20.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.10): dependencies: '@better-auth/core': 1.4.22(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.2)(kysely@0.28.15)(nanostores@1.2.0) '@better-auth/telemetry': 1.4.22(@better-auth/core@1.4.22(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.2.2)(kysely@0.28.15)(nanostores@1.2.0)) @@ -21202,11 +21989,11 @@ snapshots: zod: 4.3.6 optionalDependencies: better-sqlite3: 12.8.0 - next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) pg: 8.20.0 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) better-call@1.1.8(zod@4.3.6): dependencies: @@ -21352,20 +22139,11 @@ snapshots: caniuse-lite@1.0.30001787: {} - canvas@2.11.2: - dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - nan: 2.26.2 - simple-get: 3.1.1 - transitivePeerDependencies: - - encoding - - supports-color - optional: true - canvas@3.1.0: dependencies: node-addon-api: 7.1.1 prebuild-install: 7.1.3 + optional: true ccount@2.0.1: {} @@ -21420,13 +22198,8 @@ snapshots: chownr@1.1.4: {} - chownr@2.0.0: - optional: true - chrome-trace-event@1.0.4: {} - ci-info@4.4.0: {} - citty@0.1.6: dependencies: consola: 3.4.2 @@ -21489,13 +22262,11 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 + color-name@2.1.0: {} - color-support@1.1.3: - optional: true + color-string@2.1.4: + dependencies: + color-name: 2.1.0 combined-stream@1.0.8: dependencies: @@ -21507,6 +22278,10 @@ snapshots: commander@2.20.3: {} + commander@7.2.0: {} + + commander@8.3.0: {} + commondir@1.0.1: {} compressible@2.0.18: @@ -21555,9 +22330,6 @@ snapshots: consola@3.4.2: {} - console-control-strings@1.1.0: - optional: true - content-disposition@0.5.2: {} convert-gitmoji@0.1.5: {} @@ -21577,6 +22349,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -21595,8 +22375,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-js@4.2.0: {} - css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -21604,29 +22382,109 @@ snapshots: cssesc@3.0.0: {} - cssstyle@4.6.0: + csstype@3.2.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): dependencies: - '@asamuzakjp/css-color': 3.2.0 - rrweb-cssom: 0.8.0 + cose-base: 1.0.3 + cytoscape: 3.34.0 - csstype@3.2.3: {} + cytoscape-fcose@2.2.0(cytoscape@3.34.0): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.0 + + cytoscape@3.34.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 d3-array@3.2.4: dependencies: internmap: 2.0.3 + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + d3-format@3.1.2: {} + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-path@1.0.9: {} + d3-path@3.1.0: {} + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + d3-scale@4.0.2: dependencies: d3-array: 3.2.4 @@ -21635,6 +22493,12 @@ snapshots: d3-time: 3.1.0 d3-time-format: 4.1.0 + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + d3-shape@3.2.0: dependencies: d3-path: 3.1.0 @@ -21649,10 +22513,60 @@ snapshots: d3-timer@3.0.1: {} - data-urls@5.0.0: + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: dependencies: - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 + d3: 7.9.0 + lodash-es: 4.18.1 data-urls@7.0.0(@noble/hashes@2.0.1): dependencies: @@ -21683,6 +22597,8 @@ snapshots: date-fns@4.1.0: {} + dayjs@1.11.21: {} + debounce-fn@6.0.0: dependencies: mimic-function: 5.0.1 @@ -21709,11 +22625,6 @@ snapshots: dependencies: character-entities: 2.0.2 - decompress-response@4.2.1: - dependencies: - mimic-response: 2.1.0 - optional: true - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -21753,10 +22664,11 @@ snapshots: defu@6.1.6: {} - delayed-stream@1.0.0: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 - delegates@1.0.0: - optional: true + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -21806,6 +22718,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.4.12: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -21857,7 +22773,7 @@ snapshots: engine.io@6.6.6: dependencies: '@types/cors': 2.8.19 - '@types/node': 22.13.13 + '@types/node': 25.6.0 '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 @@ -21954,8 +22870,6 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} - es-module-lexer@2.1.0: {} es-object-atoms@1.1.1: @@ -22055,8 +22969,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@2.0.0: {} - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -22160,6 +23072,8 @@ snapshots: transitivePeerDependencies: - supports-color + esm@3.2.25: {} + espree@10.4.0: dependencies: acorn: 8.16.0 @@ -22249,15 +23163,6 @@ snapshots: expect-type@1.3.0: {} - expect@30.3.0: - dependencies: - '@jest/expect-utils': 30.3.0 - '@jest/get-type': 30.1.0 - jest-matcher-utils: 30.3.0 - jest-message-util: 30.3.0 - jest-mock: 30.3.0 - jest-util: 30.3.0 - exsolve@1.0.8: {} extend-shallow@2.0.1: @@ -22365,25 +23270,17 @@ snapshots: fs-constants@1.0.0: {} - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - optional: true - - fs.realpath@1.0.0: - optional: true - fsevents@2.3.2: optional: true fsevents@2.3.3: optional: true - fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6): + fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6): dependencies: '@formatjs/intl-localematcher': 0.8.2 '@orama/orama': 3.1.18 - '@shikijs/rehype': 3.23.0 + '@shikijs/rehype': 4.4.3 '@shikijs/transformers': 3.23.0 estree-util-value-to-estree: 3.5.0 github-slugger: 2.0.0 @@ -22397,27 +23294,27 @@ snapshots: remark-gfm: 4.0.1 remark-rehype: 11.1.2 scroll-into-view-if-needed: 3.1.0 - shiki: 3.23.0 + shiki: 4.4.3 tinyglobby: 0.2.16 unist-util-visit: 5.1.0 optionalDependencies: '@types/react': 19.2.14 lucide-react: 0.562.0(react@19.2.5) - next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 react-dom: 19.2.5(react@19.2.5) zod: 4.3.6 transitivePeerDependencies: - supports-color - fumadocs-mdx@14.2.11(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)): + fumadocs-mdx@14.2.11(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.27.5 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) js-yaml: 4.1.1 mdast-util-mdx: 3.0.0 mdast-util-to-markdown: 2.1.2 @@ -22434,24 +23331,24 @@ snapshots: '@types/mdast': 4.0.4 '@types/mdx': 2.0.13 '@types/react': 19.2.14 - next: 16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - fumadocs-twoslash@3.1.15(@fumadocs/base-ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3): + fumadocs-twoslash@3.1.15(@fumadocs/base-ui@16.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@7.0.2): dependencies: - '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@shikijs/twoslash': 4.0.2(typescript@5.9.3) - fumadocs-ui: '@fumadocs/base-ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)' + '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@shikijs/twoslash': 4.0.2(typescript@7.0.2) + fumadocs-ui: '@fumadocs/base-ui@16.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)' mdast-util-from-markdown: 2.0.3 mdast-util-gfm: 3.1.0 mdast-util-to-hast: 13.2.1 react: 19.2.5 - shiki: 4.0.2 + shiki: 4.4.3 tailwind-merge: 3.5.0 - twoslash: 0.3.6(typescript@5.9.3) + twoslash: 0.3.6(typescript@7.0.2) optionalDependencies: '@types/react': 19.2.14 transitivePeerDependencies: @@ -22460,26 +23357,26 @@ snapshots: - supports-color - typescript - fumadocs-typescript@5.2.1(7b71d35b2307cf0dedaa2cbc5003f54e): + fumadocs-typescript@5.2.1(c2cf99cf8c13a0f5fb978e1c3d570ec2): dependencies: estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + fumadocs-core: 16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) hast-util-to-estree: 3.1.3 hast-util-to-jsx-runtime: 2.3.6 react: 19.2.5 remark: 15.0.1 remark-rehype: 11.1.2 ts-morph: 27.0.2 - typescript: 5.9.3 + typescript: 7.0.2 unified: 11.0.5 unist-util-visit: 5.1.0 optionalDependencies: '@types/estree': 1.0.8 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 '@types/react': 19.2.14 - fumadocs-ui: '@fumadocs/base-ui@16.5.0(@types/react@19.2.14)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)' - shiki: 4.0.2 + fumadocs-ui: '@fumadocs/base-ui@16.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(fumadocs-core@16.5.0(@types/react@19.2.14)(lucide-react@0.562.0(react@19.2.5))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.2)' + shiki: 4.4.3 transitivePeerDependencies: - supports-color @@ -22496,19 +23393,6 @@ snapshots: functions-have-names@1.2.3: {} - gauge@3.0.2: - dependencies: - aproba: 2.1.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - optional: true - generator-function@2.0.1: {} gensync@1.0.0-beta.2: {} @@ -22539,8 +23423,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stdin@5.0.1: {} - get-stream@6.0.1: {} get-symbol-description@1.1.0: @@ -22582,16 +23464,6 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - optional: true - globals@11.12.0: {} globals@14.0.0: {} @@ -22601,8 +23473,6 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 - glur@1.1.2: {} - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -22616,6 +23486,8 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 + hachure-fill@0.5.2: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -22632,10 +23504,7 @@ snapshots: has-tostringtag@1.0.2: dependencies: - has-symbols: 1.1.0 - - has-unicode@2.0.1: - optional: true + has-symbols: 1.1.0 hash.js@1.1.7: dependencies: @@ -22650,7 +23519,7 @@ snapshots: dependencies: '@types/estree': 1.0.8 '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 comma-separated-tokens: 2.0.3 devlop: 1.1.0 estree-util-attach-comments: 3.0.0 @@ -22669,7 +23538,7 @@ snapshots: hast-util-to-html@9.0.5: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/unist': 3.0.3 ccount: 2.0.1 comma-separated-tokens: 2.0.3 @@ -22684,7 +23553,7 @@ snapshots: hast-util-to-jsx-runtime@2.3.6: dependencies: '@types/estree': 1.0.8 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 devlop: 1.1.0 @@ -22703,11 +23572,11 @@ snapshots: hast-util-to-string@3.0.1: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 hast-util-whitespace@3.0.0: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 headers-polyfill@4.0.3: {} @@ -22723,10 +23592,6 @@ snapshots: hsl-to-rgb-for-reals@1.1.1: {} - html-encoding-sniffer@4.0.0: - dependencies: - whatwg-encoding: 3.1.1 - html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1): dependencies: '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) @@ -22754,13 +23619,6 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -22768,13 +23626,6 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - human-signals@2.1.0: {} hyphen@1.14.1: {} @@ -22819,13 +23670,9 @@ snapshots: cjs-module-lexer: 2.2.0 module-details-from-path: 1.0.4 - imurmurhash@0.1.4: {} + import-meta-resolve@4.2.0: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - optional: true + imurmurhash@0.1.4: {} inherits@2.0.4: {} @@ -22844,6 +23691,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + internmap@2.0.3: {} is-alphabetical@2.0.1: {} @@ -22861,8 +23710,6 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.4: {} - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -23037,62 +23884,9 @@ snapshots: dependencies: restructure: 3.0.2 - jest-diff@30.3.0: - dependencies: - '@jest/diff-sequences': 30.3.0 - '@jest/get-type': 30.1.0 - chalk: 4.1.2 - pretty-format: 30.3.0 - - jest-image-snapshot@6.5.2: - dependencies: - chalk: 4.1.2 - get-stdin: 5.0.1 - glur: 1.1.2 - lodash: 4.18.1 - pixelmatch: 5.3.0 - pngjs: 3.4.0 - ssim.js: 3.5.0 - - jest-matcher-utils@30.3.0: - dependencies: - '@jest/get-type': 30.1.0 - chalk: 4.1.2 - jest-diff: 30.3.0 - pretty-format: 30.3.0 - - jest-message-util@30.3.0: - dependencies: - '@babel/code-frame': 7.29.0 - '@jest/types': 30.3.0 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - picomatch: 4.0.4 - pretty-format: 30.3.0 - slash: 3.0.0 - stack-utils: 2.0.6 - - jest-mock@30.3.0: - dependencies: - '@jest/types': 30.3.0 - '@types/node': 22.13.13 - jest-util: 30.3.0 - - jest-regex-util@30.0.1: {} - - jest-util@30.3.0: - dependencies: - '@jest/types': 30.3.0 - '@types/node': 22.13.13 - chalk: 4.1.2 - ci-info: 4.4.0 - graceful-fs: 4.2.11 - picomatch: 4.0.4 - jest-worker@27.5.1: dependencies: - '@types/node': 22.13.13 + '@types/node': 25.6.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -23114,6 +23908,8 @@ snapshots: js-base64@3.7.8: {} + js-md5@0.8.3: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: @@ -23125,36 +23921,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@25.0.1(canvas@2.11.2): - 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.23 - parse5: 7.3.0 - 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.20.0 - xml-name-validator: 5.0.0 - optionalDependencies: - canvas: 2.11.2 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0): dependencies: '@asamuzakjp/css-color': 5.1.11 @@ -23214,16 +23980,26 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + katex@0.16.47(patch_hash=cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7): + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + kind-of@6.0.3: {} kleur@3.0.3: {} kysely@0.28.15: {} + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + leac@0.6.0: {} level-codec@9.0.2: @@ -23302,36 +24078,69 @@ snapshots: lightningcss-android-arm64@1.32.0: optional: true + lightningcss-android-arm64@1.33.0: + optional: true + lightningcss-darwin-arm64@1.32.0: optional: true + lightningcss-darwin-arm64@1.33.0: + optional: true + lightningcss-darwin-x64@1.32.0: optional: true + lightningcss-darwin-x64@1.33.0: + optional: true + lightningcss-freebsd-x64@1.32.0: optional: true + lightningcss-freebsd-x64@1.33.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + lightningcss-linux-arm64-gnu@1.32.0: optional: true + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + lightningcss-linux-arm64-musl@1.32.0: optional: true + lightningcss-linux-arm64-musl@1.33.0: + optional: true + lightningcss-linux-x64-gnu@1.32.0: optional: true + lightningcss-linux-x64-gnu@1.33.0: + optional: true + lightningcss-linux-x64-musl@1.32.0: optional: true + lightningcss-linux-x64-musl@1.33.0: + optional: true + lightningcss-win32-arm64-msvc@1.32.0: optional: true + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + lightningcss-win32-x64-msvc@1.32.0: optional: true + lightningcss-win32-x64-msvc@1.33.0: + optional: true + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 @@ -23348,6 +24157,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + linebreak@1.1.0: dependencies: base64-js: 0.0.8 @@ -23361,6 +24186,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + lodash.debounce@4.0.8: {} lodash.isequal@4.5.0: {} @@ -23418,11 +24245,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-dir@3.1.0: - dependencies: - semver: 6.3.1 - optional: true - markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -23433,8 +24255,19 @@ snapshots: marked@15.0.12: {} + marked@16.4.2: {} + math-intrinsics@1.1.0: {} + mathjax-full@3.2.2: + dependencies: + esm: 3.2.25 + mhchemparser: 4.2.1 + mj-context-menu: 0.6.1 + speech-rule-engine: 4.1.4 + + mathml2omml@0.5.0: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -23519,7 +24352,7 @@ snapshots: mdast-util-mdx-expression@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.0.3 @@ -23530,7 +24363,7 @@ snapshots: mdast-util-mdx-jsx@3.2.0: dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 '@types/unist': 3.0.3 ccount: 2.0.1 @@ -23557,7 +24390,7 @@ snapshots: mdast-util-mdxjs-esm@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.0.3 @@ -23572,7 +24405,7 @@ snapshots: mdast-util-to-hast@13.2.1: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 '@ungap/structured-clone': 1.3.0 devlop: 1.1.0 @@ -23606,6 +24439,32 @@ snapshots: merge-stream@2.0.0: {} + mermaid@11.16.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.4 + '@mermaid-js/parser': 1.2.0 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.21 + dompurify: 3.4.12 + es-toolkit: 1.45.1 + katex: 0.16.47(patch_hash=cbfb6fe178282ddb73b753dcb27f891295e4f9ed85f63bc1466b4e4b1e4ef6e7) + khroma: 2.1.0 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 14.0.1 + + mhchemparser@4.2.1: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -23896,9 +24755,6 @@ snapshots: mimic-function@5.0.1: {} - mimic-response@2.1.0: - optional: true - mimic-response@3.1.0: {} minimalistic-assert@1.0.1: {} @@ -23917,27 +24773,12 @@ snapshots: minimist@1.2.8: {} - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - optional: true - - minipass@5.0.0: - optional: true - minipass@7.1.3: {} - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - optional: true + mj-context-menu@0.6.1: {} mkdirp-classic@0.5.3: {} - mkdirp@1.0.4: - optional: true - module-details-from-path@1.0.4: {} motion-dom@12.38.0: @@ -23963,62 +24804,11 @@ snapshots: ms@2.1.3: {} - msw-snapshot@5.3.0(msw@2.11.5(@types/node@22.13.13)(typescript@5.9.3)): - dependencies: - msw: 2.11.5(@types/node@22.13.13)(typescript@5.9.3) - - msw@2.11.5(@types/node@20.19.39)(typescript@5.9.3): - dependencies: - '@inquirer/confirm': 5.1.21(@types/node@20.19.39) - '@mswjs/interceptors': 0.39.8 - '@open-draft/deferred-promise': 2.2.0 - '@types/statuses': 2.0.6 - cookie: 1.1.1 - graphql: 16.13.2 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - rettime: 0.7.0 - statuses: 2.0.2 - strict-event-emitter: 0.5.1 - tough-cookie: 6.0.1 - type-fest: 4.41.0 - until-async: 3.0.2 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - '@types/node' - optional: true - - msw@2.11.5(@types/node@22.13.13)(typescript@5.9.3): + msw-snapshot@5.3.0(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2)): dependencies: - '@inquirer/confirm': 5.1.21(@types/node@22.13.13) - '@mswjs/interceptors': 0.39.8 - '@open-draft/deferred-promise': 2.2.0 - '@types/statuses': 2.0.6 - cookie: 1.1.1 - graphql: 16.13.2 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - rettime: 0.7.0 - statuses: 2.0.2 - strict-event-emitter: 0.5.1 - tough-cookie: 6.0.1 - type-fest: 4.41.0 - until-async: 3.0.2 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - '@types/node' + msw: 2.11.5(@types/node@25.6.0)(typescript@7.0.2) - msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3): + msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2): dependencies: '@inquirer/confirm': 5.1.21(@types/node@25.6.0) '@mswjs/interceptors': 0.39.8 @@ -24039,20 +24829,18 @@ snapshots: until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: - typescript: 5.9.3 + typescript: 7.0.2 transitivePeerDependencies: - '@types/node' - optional: true mute-stream@2.0.0: {} namespace-emitter@2.0.1: {} - nan@2.26.2: - optional: true - nanoid@3.3.12: {} + nanoid@3.3.18: {} + nanoid@4.0.2: {} nanoid@5.1.7: {} @@ -24091,38 +24879,40 @@ snapshots: transitivePeerDependencies: - supports-color - next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + next@16.3.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(@types/node@25.6.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - '@next/env': 16.2.7 + '@next/env': 16.3.0 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.17 caniuse-lite: 1.0.30001787 - postcss: 8.4.31 + postcss: 8.5.23 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.7 - '@next/swc-darwin-x64': 16.2.7 - '@next/swc-linux-arm64-gnu': 16.2.7 - '@next/swc-linux-arm64-musl': 16.2.7 - '@next/swc-linux-x64-gnu': 16.2.7 - '@next/swc-linux-x64-musl': 16.2.7 - '@next/swc-win32-arm64-msvc': 16.2.7 - '@next/swc-win32-x64-msvc': 16.2.7 + '@next/swc-darwin-arm64': 16.3.0 + '@next/swc-darwin-x64': 16.3.0 + '@next/swc-linux-arm64-gnu': 16.3.0 + '@next/swc-linux-arm64-musl': 16.3.0 + '@next/swc-linux-x64-gnu': 16.3.0 + '@next/swc-linux-x64-musl': 16.3.0 + '@next/swc-win32-arm64-msvc': 16.3.0 + '@next/swc-win32-x64-msvc': 16.3.0 '@opentelemetry/api': 1.9.1 '@playwright/test': 1.60.0 babel-plugin-react-compiler: 1.0.0 - sharp: 0.34.5 + sharp: 0.35.3(@types/node@25.6.0) transitivePeerDependencies: - '@babel/core' + - '@types/node' - babel-plugin-macros node-abi@3.89.0: dependencies: - semver: 7.7.4 + semver: 7.8.5 - node-addon-api@7.1.1: {} + node-addon-api@7.1.1: + optional: true node-exports-info@1.6.0: dependencies: @@ -24144,11 +24934,6 @@ snapshots: nodemailer@7.0.13: {} - nopt@5.0.0: - dependencies: - abbrev: 1.1.1 - optional: true - normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -24163,16 +24948,6 @@ snapshots: npm-to-yarn@3.0.1: {} - npmlog@5.0.1: - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - optional: true - - nwsapi@2.2.23: {} - nypm@0.6.2: dependencies: citty: 0.1.6 @@ -24247,11 +25022,11 @@ snapshots: dependencies: mimic-function: 5.0.1 - oniguruma-parser@0.12.1: {} + oniguruma-parser@0.12.2: {} - oniguruma-to-es@4.3.5: + oniguruma-to-es@4.3.6: dependencies: - oniguruma-parser: 0.12.1 + oniguruma-parser: 0.12.2 regex: 6.1.0 regex-recursion: 6.0.2 @@ -24302,308 +25077,63 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxfmt@0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.52.0 - '@oxfmt/binding-android-arm64': 0.52.0 - '@oxfmt/binding-darwin-arm64': 0.52.0 - '@oxfmt/binding-darwin-x64': 0.52.0 - '@oxfmt/binding-freebsd-x64': 0.52.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 - '@oxfmt/binding-linux-arm64-gnu': 0.52.0 - '@oxfmt/binding-linux-arm64-musl': 0.52.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-musl': 0.52.0 - '@oxfmt/binding-linux-s390x-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-musl': 0.52.0 - '@oxfmt/binding-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-msvc': 0.52.0 - '@oxfmt/binding-win32-x64-msvc': 0.52.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxfmt@0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.52.0 - '@oxfmt/binding-android-arm64': 0.52.0 - '@oxfmt/binding-darwin-arm64': 0.52.0 - '@oxfmt/binding-darwin-x64': 0.52.0 - '@oxfmt/binding-freebsd-x64': 0.52.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 - '@oxfmt/binding-linux-arm64-gnu': 0.52.0 - '@oxfmt/binding-linux-arm64-musl': 0.52.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-musl': 0.52.0 - '@oxfmt/binding-linux-s390x-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-musl': 0.52.0 - '@oxfmt/binding-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-msvc': 0.52.0 - '@oxfmt/binding-win32-x64-msvc': 0.52.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxfmt@0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.52.0 - '@oxfmt/binding-android-arm64': 0.52.0 - '@oxfmt/binding-darwin-arm64': 0.52.0 - '@oxfmt/binding-darwin-x64': 0.52.0 - '@oxfmt/binding-freebsd-x64': 0.52.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 - '@oxfmt/binding-linux-arm64-gnu': 0.52.0 - '@oxfmt/binding-linux-arm64-musl': 0.52.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-musl': 0.52.0 - '@oxfmt/binding-linux-s390x-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-musl': 0.52.0 - '@oxfmt/binding-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-msvc': 0.52.0 - '@oxfmt/binding-win32-x64-msvc': 0.52.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxfmt@0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.52.0 - '@oxfmt/binding-android-arm64': 0.52.0 - '@oxfmt/binding-darwin-arm64': 0.52.0 - '@oxfmt/binding-darwin-x64': 0.52.0 - '@oxfmt/binding-freebsd-x64': 0.52.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 - '@oxfmt/binding-linux-arm64-gnu': 0.52.0 - '@oxfmt/binding-linux-arm64-musl': 0.52.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-musl': 0.52.0 - '@oxfmt/binding-linux-s390x-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-musl': 0.52.0 - '@oxfmt/binding-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-msvc': 0.52.0 - '@oxfmt/binding-win32-x64-msvc': 0.52.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxfmt@0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.52.0 - '@oxfmt/binding-android-arm64': 0.52.0 - '@oxfmt/binding-darwin-arm64': 0.52.0 - '@oxfmt/binding-darwin-x64': 0.52.0 - '@oxfmt/binding-freebsd-x64': 0.52.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 - '@oxfmt/binding-linux-arm64-gnu': 0.52.0 - '@oxfmt/binding-linux-arm64-musl': 0.52.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-musl': 0.52.0 - '@oxfmt/binding-linux-s390x-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-musl': 0.52.0 - '@oxfmt/binding-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-msvc': 0.52.0 - '@oxfmt/binding-win32-x64-msvc': 0.52.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxfmt@0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): + oxfmt@0.62.0(vite-plus@0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.52.0 - '@oxfmt/binding-android-arm64': 0.52.0 - '@oxfmt/binding-darwin-arm64': 0.52.0 - '@oxfmt/binding-darwin-x64': 0.52.0 - '@oxfmt/binding-freebsd-x64': 0.52.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 - '@oxfmt/binding-linux-arm64-gnu': 0.52.0 - '@oxfmt/binding-linux-arm64-musl': 0.52.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-musl': 0.52.0 - '@oxfmt/binding-linux-s390x-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-musl': 0.52.0 - '@oxfmt/binding-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-msvc': 0.52.0 - '@oxfmt/binding-win32-x64-msvc': 0.52.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxlint-tsgolint@0.23.0: + '@oxfmt/binding-android-arm-eabi': 0.62.0 + '@oxfmt/binding-android-arm64': 0.62.0 + '@oxfmt/binding-darwin-arm64': 0.62.0 + '@oxfmt/binding-darwin-x64': 0.62.0 + '@oxfmt/binding-freebsd-x64': 0.62.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.62.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.62.0 + '@oxfmt/binding-linux-arm64-gnu': 0.62.0 + '@oxfmt/binding-linux-arm64-musl': 0.62.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.62.0 + '@oxfmt/binding-linux-riscv64-musl': 0.62.0 + '@oxfmt/binding-linux-s390x-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-gnu': 0.62.0 + '@oxfmt/binding-linux-x64-musl': 0.62.0 + '@oxfmt/binding-openharmony-arm64': 0.62.0 + '@oxfmt/binding-win32-arm64-msvc': 0.62.0 + '@oxfmt/binding-win32-ia32-msvc': 0.62.0 + '@oxfmt/binding-win32-x64-msvc': 0.62.0 + vite-plus: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + + oxlint-tsgolint@7.0.2001: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.23.0 - '@oxlint-tsgolint/darwin-x64': 0.23.0 - '@oxlint-tsgolint/linux-arm64': 0.23.0 - '@oxlint-tsgolint/linux-x64': 0.23.0 - '@oxlint-tsgolint/win32-arm64': 0.23.0 - '@oxlint-tsgolint/win32-x64': 0.23.0 - - oxlint@1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): + '@oxlint-tsgolint/darwin-arm64': 7.0.2001 + '@oxlint-tsgolint/darwin-x64': 7.0.2001 + '@oxlint-tsgolint/linux-arm64': 7.0.2001 + '@oxlint-tsgolint/linux-x64': 7.0.2001 + '@oxlint-tsgolint/win32-arm64': 7.0.2001 + '@oxlint-tsgolint/win32-x64': 7.0.2001 + + oxlint@1.77.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.67.0 - '@oxlint/binding-android-arm64': 1.67.0 - '@oxlint/binding-darwin-arm64': 1.67.0 - '@oxlint/binding-darwin-x64': 1.67.0 - '@oxlint/binding-freebsd-x64': 1.67.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 - '@oxlint/binding-linux-arm-musleabihf': 1.67.0 - '@oxlint/binding-linux-arm64-gnu': 1.67.0 - '@oxlint/binding-linux-arm64-musl': 1.67.0 - '@oxlint/binding-linux-ppc64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-musl': 1.67.0 - '@oxlint/binding-linux-s390x-gnu': 1.67.0 - '@oxlint/binding-linux-x64-gnu': 1.67.0 - '@oxlint/binding-linux-x64-musl': 1.67.0 - '@oxlint/binding-openharmony-arm64': 1.67.0 - '@oxlint/binding-win32-arm64-msvc': 1.67.0 - '@oxlint/binding-win32-ia32-msvc': 1.67.0 - '@oxlint/binding-win32-x64-msvc': 1.67.0 - oxlint-tsgolint: 0.23.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxlint@1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.67.0 - '@oxlint/binding-android-arm64': 1.67.0 - '@oxlint/binding-darwin-arm64': 1.67.0 - '@oxlint/binding-darwin-x64': 1.67.0 - '@oxlint/binding-freebsd-x64': 1.67.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 - '@oxlint/binding-linux-arm-musleabihf': 1.67.0 - '@oxlint/binding-linux-arm64-gnu': 1.67.0 - '@oxlint/binding-linux-arm64-musl': 1.67.0 - '@oxlint/binding-linux-ppc64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-musl': 1.67.0 - '@oxlint/binding-linux-s390x-gnu': 1.67.0 - '@oxlint/binding-linux-x64-gnu': 1.67.0 - '@oxlint/binding-linux-x64-musl': 1.67.0 - '@oxlint/binding-openharmony-arm64': 1.67.0 - '@oxlint/binding-win32-arm64-msvc': 1.67.0 - '@oxlint/binding-win32-ia32-msvc': 1.67.0 - '@oxlint/binding-win32-x64-msvc': 1.67.0 - oxlint-tsgolint: 0.23.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxlint@1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.67.0 - '@oxlint/binding-android-arm64': 1.67.0 - '@oxlint/binding-darwin-arm64': 1.67.0 - '@oxlint/binding-darwin-x64': 1.67.0 - '@oxlint/binding-freebsd-x64': 1.67.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 - '@oxlint/binding-linux-arm-musleabihf': 1.67.0 - '@oxlint/binding-linux-arm64-gnu': 1.67.0 - '@oxlint/binding-linux-arm64-musl': 1.67.0 - '@oxlint/binding-linux-ppc64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-musl': 1.67.0 - '@oxlint/binding-linux-s390x-gnu': 1.67.0 - '@oxlint/binding-linux-x64-gnu': 1.67.0 - '@oxlint/binding-linux-x64-musl': 1.67.0 - '@oxlint/binding-openharmony-arm64': 1.67.0 - '@oxlint/binding-win32-arm64-msvc': 1.67.0 - '@oxlint/binding-win32-ia32-msvc': 1.67.0 - '@oxlint/binding-win32-x64-msvc': 1.67.0 - oxlint-tsgolint: 0.23.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxlint@1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.67.0 - '@oxlint/binding-android-arm64': 1.67.0 - '@oxlint/binding-darwin-arm64': 1.67.0 - '@oxlint/binding-darwin-x64': 1.67.0 - '@oxlint/binding-freebsd-x64': 1.67.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 - '@oxlint/binding-linux-arm-musleabihf': 1.67.0 - '@oxlint/binding-linux-arm64-gnu': 1.67.0 - '@oxlint/binding-linux-arm64-musl': 1.67.0 - '@oxlint/binding-linux-ppc64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-musl': 1.67.0 - '@oxlint/binding-linux-s390x-gnu': 1.67.0 - '@oxlint/binding-linux-x64-gnu': 1.67.0 - '@oxlint/binding-linux-x64-musl': 1.67.0 - '@oxlint/binding-openharmony-arm64': 1.67.0 - '@oxlint/binding-win32-arm64-msvc': 1.67.0 - '@oxlint/binding-win32-ia32-msvc': 1.67.0 - '@oxlint/binding-win32-x64-msvc': 1.67.0 - oxlint-tsgolint: 0.23.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxlint@1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.67.0 - '@oxlint/binding-android-arm64': 1.67.0 - '@oxlint/binding-darwin-arm64': 1.67.0 - '@oxlint/binding-darwin-x64': 1.67.0 - '@oxlint/binding-freebsd-x64': 1.67.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 - '@oxlint/binding-linux-arm-musleabihf': 1.67.0 - '@oxlint/binding-linux-arm64-gnu': 1.67.0 - '@oxlint/binding-linux-arm64-musl': 1.67.0 - '@oxlint/binding-linux-ppc64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-musl': 1.67.0 - '@oxlint/binding-linux-s390x-gnu': 1.67.0 - '@oxlint/binding-linux-x64-gnu': 1.67.0 - '@oxlint/binding-linux-x64-musl': 1.67.0 - '@oxlint/binding-openharmony-arm64': 1.67.0 - '@oxlint/binding-win32-arm64-msvc': 1.67.0 - '@oxlint/binding-win32-ia32-msvc': 1.67.0 - '@oxlint/binding-win32-x64-msvc': 1.67.0 - oxlint-tsgolint: 0.23.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - - oxlint@1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.67.0 - '@oxlint/binding-android-arm64': 1.67.0 - '@oxlint/binding-darwin-arm64': 1.67.0 - '@oxlint/binding-darwin-x64': 1.67.0 - '@oxlint/binding-freebsd-x64': 1.67.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 - '@oxlint/binding-linux-arm-musleabihf': 1.67.0 - '@oxlint/binding-linux-arm64-gnu': 1.67.0 - '@oxlint/binding-linux-arm64-musl': 1.67.0 - '@oxlint/binding-linux-ppc64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-gnu': 1.67.0 - '@oxlint/binding-linux-riscv64-musl': 1.67.0 - '@oxlint/binding-linux-s390x-gnu': 1.67.0 - '@oxlint/binding-linux-x64-gnu': 1.67.0 - '@oxlint/binding-linux-x64-musl': 1.67.0 - '@oxlint/binding-openharmony-arm64': 1.67.0 - '@oxlint/binding-win32-arm64-msvc': 1.67.0 - '@oxlint/binding-win32-ia32-msvc': 1.67.0 - '@oxlint/binding-win32-x64-msvc': 1.67.0 - oxlint-tsgolint: 0.23.0 - vite-plus: 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) + '@oxlint/binding-android-arm-eabi': 1.77.0 + '@oxlint/binding-android-arm64': 1.77.0 + '@oxlint/binding-darwin-arm64': 1.77.0 + '@oxlint/binding-darwin-x64': 1.77.0 + '@oxlint/binding-freebsd-x64': 1.77.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.77.0 + '@oxlint/binding-linux-arm-musleabihf': 1.77.0 + '@oxlint/binding-linux-arm64-gnu': 1.77.0 + '@oxlint/binding-linux-arm64-musl': 1.77.0 + '@oxlint/binding-linux-ppc64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-gnu': 1.77.0 + '@oxlint/binding-linux-riscv64-musl': 1.77.0 + '@oxlint/binding-linux-s390x-gnu': 1.77.0 + '@oxlint/binding-linux-x64-gnu': 1.77.0 + '@oxlint/binding-linux-x64-musl': 1.77.0 + '@oxlint/binding-openharmony-arm64': 1.77.0 + '@oxlint/binding-win32-arm64-msvc': 1.77.0 + '@oxlint/binding-win32-ia32-msvc': 1.77.0 + '@oxlint/binding-win32-x64-msvc': 1.77.0 + oxlint-tsgolint: 7.0.2001 + vite-plus: 0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) p-limit@3.1.0: dependencies: @@ -24676,13 +25206,12 @@ snapshots: path-browserify@1.0.1: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-expression-matcher@1.2.0: {} - path-is-absolute@1.0.1: - optional: true - path-is-inside@1.0.2: {} path-key@3.1.1: {} @@ -24707,26 +25236,9 @@ snapshots: path-type@4.0.0: {} - path2d@0.2.2: - optional: true - pathe@2.0.3: {} - pdf-to-img@4.5.0: - dependencies: - canvas: 3.1.0 - pdfjs-dist: 4.2.67 - transitivePeerDependencies: - - encoding - - supports-color - - pdfjs-dist@4.2.67: - optionalDependencies: - canvas: 2.11.2 - path2d: 0.2.2 - transitivePeerDependencies: - - encoding - - supports-color + pdfjs-dist@4.10.38: {} peberminta@0.9.0: {} @@ -24771,14 +25283,6 @@ snapshots: picomatch@4.0.4: {} - pixelmatch@5.3.0: - dependencies: - pngjs: 6.0.0 - - pixelmatch@7.2.0: - dependencies: - pngjs: 7.0.0 - pkg-types@2.3.0: dependencies: confbox: 0.2.4 @@ -24793,12 +25297,19 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - pngjs@3.4.0: {} - - pngjs@6.0.0: {} + png-js@2.0.0: + dependencies: + fflate: 0.8.3 pngjs@7.0.0: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + possible-typed-array-names@1.1.0: {} postcss-selector-parser@7.1.1: @@ -24808,15 +25319,15 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.31: + postcss@8.5.14: dependencies: nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.14: + postcss@8.5.23: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -24859,12 +25370,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - pretty-format@30.3.0: - dependencies: - '@jest/schemas': 30.0.5 - ansi-styles: 5.2.0 - react-is: 18.3.1 - prismjs@1.30.0: {} process-nextick-args@2.0.1: {} @@ -24907,10 +25412,10 @@ snapshots: prosemirror-state: 1.4.4 prosemirror-view: 1.42.2 - prosemirror-highlight@0.15.1(@shikijs/types@4.0.2)(@types/hast@3.0.4)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2): + prosemirror-highlight@0.15.3(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2): optionalDependencies: - '@shikijs/types': 4.0.2 - '@types/hast': 3.0.4 + '@shikijs/types': 4.4.3 + '@types/hast': 3.0.5 prosemirror-model: 1.25.11 prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 @@ -25126,8 +25631,6 @@ snapshots: react-is@17.0.2: {} - react-is@18.3.1: {} - react-is@19.2.4: {} react-medium-image-zoom@5.4.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5): @@ -25330,7 +25833,7 @@ snapshots: rehype-recma@1.0.0: dependencies: '@types/estree': 1.0.8 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 hast-util-to-estree: 3.1.3 transitivePeerDependencies: - supports-color @@ -25364,7 +25867,7 @@ snapshots: remark-rehype@11.1.2: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 mdast-util-to-hast: 13.2.1 unified: 11.0.5 @@ -25431,15 +25934,12 @@ snapshots: rettime@0.7.0: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - optional: true - rimraf@5.0.10: dependencies: glob: 10.5.0 + robust-predicates@3.0.3: {} + rolldown@1.0.0-rc.15: dependencies: '@oxc-project/types': 0.124.0 @@ -25500,12 +26000,17 @@ snapshots: rou3@0.7.12: {} - rrweb-cssom@0.7.1: {} - - rrweb-cssom@0.8.0: {} + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 run-applescript@7.1.0: {} + rw@1.3.3: {} + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -25571,6 +26076,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.5: {} + serve-handler@6.1.7: dependencies: bytes: 3.0.0 @@ -25597,9 +26104,6 @@ snapshots: transitivePeerDependencies: - supports-color - set-blocking@2.0.0: - optional: true - set-cookie-parser@2.7.2: {} set-function-length@1.2.2: @@ -25626,36 +26130,38 @@ snapshots: setimmediate@1.0.5: {} - sharp@0.34.5: + sharp@0.35.3(@types/node@25.6.0): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.7.4 + semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.35.3 + '@img/sharp-darwin-x64': 0.35.3 + '@img/sharp-freebsd-wasm32': 0.35.3 + '@img/sharp-libvips-darwin-arm64': 1.3.2 + '@img/sharp-libvips-darwin-x64': 1.3.2 + '@img/sharp-libvips-linux-arm': 1.3.2 + '@img/sharp-libvips-linux-arm64': 1.3.2 + '@img/sharp-libvips-linux-ppc64': 1.3.2 + '@img/sharp-libvips-linux-riscv64': 1.3.2 + '@img/sharp-libvips-linux-s390x': 1.3.2 + '@img/sharp-libvips-linux-x64': 1.3.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + '@img/sharp-linux-arm': 0.35.3 + '@img/sharp-linux-arm64': 0.35.3 + '@img/sharp-linux-ppc64': 0.35.3 + '@img/sharp-linux-riscv64': 0.35.3 + '@img/sharp-linux-s390x': 0.35.3 + '@img/sharp-linux-x64': 0.35.3 + '@img/sharp-linuxmusl-arm64': 0.35.3 + '@img/sharp-linuxmusl-x64': 0.35.3 + '@img/sharp-webcontainers-wasm32': 0.35.3 + '@img/sharp-win32-arm64': 0.35.3 + '@img/sharp-win32-ia32': 0.35.3 + '@img/sharp-win32-x64': 0.35.3 + '@types/node': 25.6.0 optional: true shebang-command@2.0.0: @@ -25666,27 +26172,16 @@ snapshots: shell-quote@1.8.3: {} - shiki@3.23.0: - dependencies: - '@shikijs/core': 3.23.0 - '@shikijs/engine-javascript': 3.23.0 - '@shikijs/engine-oniguruma': 3.23.0 - '@shikijs/langs': 3.23.0 - '@shikijs/themes': 3.23.0 - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - shiki@4.0.2: + shiki@4.4.3: dependencies: - '@shikijs/core': 4.0.2 - '@shikijs/engine-javascript': 4.0.2 - '@shikijs/engine-oniguruma': 4.0.2 - '@shikijs/langs': 4.0.2 - '@shikijs/themes': 4.0.2 - '@shikijs/types': 4.0.2 + '@shikijs/core': 4.4.3 + '@shikijs/engine-javascript': 4.4.3 + '@shikijs/engine-oniguruma': 4.4.3 + '@shikijs/langs': 4.4.3 + '@shikijs/themes': 4.4.3 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 side-channel-list@1.0.1: dependencies: @@ -25724,23 +26219,12 @@ snapshots: simple-concat@1.0.1: {} - simple-get@3.1.1: - dependencies: - decompress-response: 4.2.1 - once: 1.4.0 - simple-concat: 1.0.1 - optional: true - simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -25749,8 +26233,6 @@ snapshots: sisteransi@1.0.5: {} - slash@3.0.0: {} - slate-history@0.110.3(slate@0.110.2): dependencies: is-plain-object: 5.0.0 @@ -25825,16 +26307,16 @@ snapshots: space-separated-tokens@2.0.2: {} + speech-rule-engine@4.1.4: + dependencies: + '@xmldom/xmldom': 0.9.10 + commander: 13.1.0 + wicked-good-xpath: 1.3.0 + split2@4.2.0: {} sprintf-js@1.0.3: {} - ssim.js@3.5.0: {} - - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - stackback@0.0.2: {} stacktrace-parser@0.1.11: @@ -25960,6 +26442,8 @@ snapshots: stylis@4.2.0: {} + stylis@4.4.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -26007,16 +26491,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - optional: true - terser-webpack-plugin@5.5.0(esbuild@0.27.5)(webpack@5.105.4(esbuild@0.27.5)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -26058,38 +26532,29 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@2.1.0: {} tinyrainbow@3.1.0: {} - tldts-core@6.1.86: {} - tldts-core@7.0.27: {} - tldts@6.1.86: - dependencies: - tldts-core: 6.1.86 - tldts@7.0.27: dependencies: tldts-core: 7.0.27 totalist@3.0.1: {} - tough-cookie@5.1.2: - dependencies: - tldts: 6.1.86 - tough-cookie@6.0.1: dependencies: tldts: 7.0.27 tr46@0.0.3: {} - tr46@5.1.1: - dependencies: - punycode: 2.3.1 - tr46@6.0.0: dependencies: punycode: 2.3.1 @@ -26100,6 +26565,8 @@ snapshots: trough@2.2.0: {} + ts-dedent@2.3.0: {} + ts-morph@27.0.2: dependencies: '@ts-morph/common': 0.28.1 @@ -26135,11 +26602,11 @@ snapshots: twoslash-protocol@0.3.6: {} - twoslash@0.3.6(typescript@5.9.3): + twoslash@0.3.6(typescript@7.0.2): dependencies: - '@typescript/vfs': 1.6.4(typescript@5.9.3) + '@typescript/vfs': 1.6.4(typescript@7.0.2) twoslash-protocol: 0.3.6 - typescript: 5.9.3 + typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -26192,6 +26659,29 @@ snapshots: typescript@5.9.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + ufo@1.6.3: {} uint8array-extras@1.5.0: {} @@ -26216,10 +26706,6 @@ snapshots: quansync: 1.0.0 unconfig-core: 7.5.0 - undici-types@6.20.0: {} - - undici-types@6.21.0: {} - undici-types@7.19.2: {} undici@6.25.0: {} @@ -26321,11 +26807,13 @@ snapshots: util-deprecate@1.0.2: {} + uuid@14.0.1: {} + uuid@9.0.1: {} - valibot@1.3.1(typescript@5.9.3): + valibot@1.3.1(typescript@7.0.2): optionalDependencies: - typescript: 5.9.3 + typescript: 7.0.2 vary@1.1.2: {} @@ -26362,13 +26850,13 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - vite-node@6.0.0(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0): + vite-node@6.0.0(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0): dependencies: cac: 7.0.0 es-module-lexer: 2.1.0 obug: 2.1.1 pathe: 2.0.3 - vite: 8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -26383,17 +26871,13 @@ snapshots: - tsx - yaml - vite-plugin-externalize-deps@0.10.0(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - vite: 8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - vite-plugin-externalize-deps@0.10.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)): dependencies: vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - vite-plugin-inspect@12.0.0-beta.1(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0): + vite-plugin-inspect@12.0.0-beta.1(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0): dependencies: - '@vitejs/devtools-kit': 0.1.13(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0) + '@vitejs/devtools-kit': 0.1.13(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(ws@8.20.0) ansis: 4.2.0 error-stack-parser-es: 1.0.5 obug: 2.1.1 @@ -26402,296 +26886,55 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - typescript - ws - vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.133.0 - '@oxlint/plugins': 1.61.0 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - '@voidzero-dev/vite-plus-test': 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - oxfmt: 0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint: 1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint-tsgolint: 0.23.0 - optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.24 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.24 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.24 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.24 - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@tsdown/css' - - '@tsdown/exe' - - '@types/node' - - '@vitejs/devtools' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - bufferutil - - esbuild - - happy-dom - - jiti - - jsdom - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - svelte - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - vite - - yaml - - vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.133.0 - '@oxlint/plugins': 1.61.0 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - '@voidzero-dev/vite-plus-test': 0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - oxfmt: 0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint: 1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint-tsgolint: 0.23.0 - optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.24 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.24 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.24 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.24 - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@tsdown/css' - - '@tsdown/exe' - - '@types/node' - - '@vitejs/devtools' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - bufferutil - - esbuild - - happy-dom - - jiti - - jsdom - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - svelte - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - vite - - yaml - - vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.133.0 - '@oxlint/plugins': 1.61.0 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - '@voidzero-dev/vite-plus-test': 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - oxfmt: 0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint: 1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint-tsgolint: 0.23.0 - optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.24 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.24 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.24 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.24 - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@tsdown/css' - - '@tsdown/exe' - - '@types/node' - - '@vitejs/devtools' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - bufferutil - - esbuild - - happy-dom - - jiti - - jsdom - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - svelte - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - vite - - yaml - - vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.133.0 - '@oxlint/plugins': 1.61.0 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - '@voidzero-dev/vite-plus-test': 0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - oxfmt: 0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint: 1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint-tsgolint: 0.23.0 - optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.24 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.24 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.24 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.24 - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@tsdown/css' - - '@tsdown/exe' - - '@types/node' - - '@vitejs/devtools' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - bufferutil - - esbuild - - happy-dom - - jiti - - jsdom - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - svelte - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - vite - - yaml - - vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.133.0 - '@oxlint/plugins': 1.61.0 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - '@voidzero-dev/vite-plus-test': 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - oxfmt: 0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint: 1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@25.0.1(canvas@2.11.2))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint-tsgolint: 0.23.0 - optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.24 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.24 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.24 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.24 - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@tsdown/css' - - '@tsdown/exe' - - '@types/node' - - '@vitejs/devtools' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - bufferutil - - esbuild - - happy-dom - - jiti - - jsdom - - less - - publint - - sass - - sass-embedded - - stylus - - sugarss - - svelte - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - vite - - yaml - - vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.133.0 - '@oxlint/plugins': 1.61.0 - '@voidzero-dev/vite-plus-core': 0.1.24(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - '@voidzero-dev/vite-plus-test': 0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) - oxfmt: 0.52.0(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint: 1.67.0(oxlint-tsgolint@0.23.0)(vite-plus@0.1.24(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(terser@5.46.2)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) - oxlint-tsgolint: 0.23.0 + vite-plus@0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.143.0 + '@oxlint/plugins': 1.73.0 + '@vitest/browser': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/browser-preview': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/ui': 4.1.5(vitest@4.1.10) + '@vitest/utils': 4.1.10 + '@voidzero-dev/vite-plus-core': 0.2.9(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(yaml@2.9.0) + oxfmt: 0.62.0(vite-plus@0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) + oxlint: 1.77.0(oxlint-tsgolint@7.0.2001)(vite-plus@0.2.9(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(terser@5.46.2)(tsx@4.21.0)(typescript@7.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2001 + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.24 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.24 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.24 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.24 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.24 + '@vitest/browser-playwright': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(playwright@1.60.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) + '@voidzero-dev/vite-plus-darwin-arm64': 0.2.9 + '@voidzero-dev/vite-plus-darwin-x64': 0.2.9 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.9 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.9 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.9 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.9 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.9 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.9 transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' - '@opentelemetry/api' - - '@tsdown/css' - - '@tsdown/exe' - '@types/node' - '@vitejs/devtools' - '@vitest/coverage-istanbul' - '@vitest/coverage-v8' - - '@vitest/ui' - bufferutil - esbuild - happy-dom - jiti - jsdom - less + - msw - publint - sass - sass-embedded @@ -26707,45 +26950,13 @@ snapshots: - vite - yaml - vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.14 - rolldown: 1.0.0-rc.15 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 20.19.39 - esbuild: 0.27.5 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.46.2 - tsx: 4.21.0 - yaml: 2.9.0 - - vite@8.0.8(@types/node@22.13.13)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.14 - rolldown: 1.0.0-rc.15 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 22.13.13 - esbuild: 0.27.5 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.46.2 - tsx: 4.21.0 - yaml: 2.9.0 - vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0): dependencies: - lightningcss: 1.32.0 + lightningcss: 1.33.0 picomatch: 4.0.4 - postcss: 8.5.14 + postcss: 8.5.23 rolldown: 1.0.0-rc.15 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.6.0 esbuild: 0.27.5 @@ -26755,54 +26966,24 @@ snapshots: tsx: 4.21.0 yaml: 2.9.0 - vitest-browser-react@2.2.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.7): + vitest-browser-react@2.2.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.10): dependencies: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@20.19.39)(typescript@5.9.3))(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@20.19.39)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@20.19.39)(typescript@5.9.3))(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(msw@2.11.5(@types/node@20.19.39)(typescript@5.9.3))(vite@8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.7 - '@vitest/runner': 4.1.7 - '@vitest/snapshot': 4.1.7 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 8.0.8(@types/node@20.19.39)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 20.19.39 - '@vitest/ui': 4.1.5(vitest@4.1.7) - jsdom: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) - transitivePeerDependencies: - - msw - - vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(@vitest/ui@4.1.5)(jsdom@29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0))(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)): dependencies: - '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(msw@2.11.5(@types/node@25.6.0)(typescript@5.9.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.7 - '@vitest/runner': 4.1.7 - '@vitest/snapshot': 4.1.7 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -26812,17 +26993,19 @@ snapshots: std-env: 4.0.0 tinybench: 2.9.0 tinyexec: 1.2.4 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 tinyrainbow: 3.1.0 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 25.6.0 + '@vitest/browser-playwright': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(playwright@1.60.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/browser-preview': 4.1.10(msw@2.11.5(@types/node@25.6.0)(typescript@7.0.2))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/ui': 4.1.5(vitest@4.1.10) jsdom: 29.0.2(@noble/hashes@2.0.1)(canvas@3.1.0) transitivePeerDependencies: - msw - optional: true w3c-keyname@2.2.8: {} @@ -26849,8 +27032,6 @@ snapshots: webidl-conversions@3.0.1: {} - webidl-conversions@7.0.0: {} - webidl-conversions@8.0.1: {} webpack-sources@3.4.1: {} @@ -26887,19 +27068,8 @@ snapshots: - esbuild - uglify-js - whatwg-encoding@3.1.1: - dependencies: - iconv-lite: 0.6.3 - - whatwg-mimetype@4.0.0: {} - whatwg-mimetype@5.0.0: {} - whatwg-url@14.2.0: - dependencies: - tr46: 5.1.1 - webidl-conversions: 7.0.0 - whatwg-url@16.0.1(@noble/hashes@2.0.1): dependencies: '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) @@ -26965,10 +27135,7 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - optional: true + wicked-good-xpath@1.3.0: {} widest-line@4.0.1: dependencies: @@ -27085,9 +27252,6 @@ snapshots: yallist@3.1.1: {} - yallist@4.0.0: - optional: true - yaml@1.10.3: {} yaml@2.9.0: {} @@ -27118,6 +27282,38 @@ snapshots: yoga-layout@3.2.1: {} + yuku-codegen@0.5.48: + dependencies: + '@yuku-toolchain/types': 0.5.43 + optionalDependencies: + '@yuku-codegen/binding-darwin-arm64': 0.5.48 + '@yuku-codegen/binding-darwin-x64': 0.5.48 + '@yuku-codegen/binding-freebsd-x64': 0.5.48 + '@yuku-codegen/binding-linux-arm-gnu': 0.5.48 + '@yuku-codegen/binding-linux-arm-musl': 0.5.48 + '@yuku-codegen/binding-linux-arm64-gnu': 0.5.48 + '@yuku-codegen/binding-linux-arm64-musl': 0.5.48 + '@yuku-codegen/binding-linux-x64-gnu': 0.5.48 + '@yuku-codegen/binding-linux-x64-musl': 0.5.48 + '@yuku-codegen/binding-win32-arm64': 0.5.48 + '@yuku-codegen/binding-win32-x64': 0.5.48 + + yuku-parser@0.5.48: + dependencies: + '@yuku-toolchain/types': 0.5.43 + optionalDependencies: + '@yuku-parser/binding-darwin-arm64': 0.5.48 + '@yuku-parser/binding-darwin-x64': 0.5.48 + '@yuku-parser/binding-freebsd-x64': 0.5.48 + '@yuku-parser/binding-linux-arm-gnu': 0.5.48 + '@yuku-parser/binding-linux-arm-musl': 0.5.48 + '@yuku-parser/binding-linux-arm64-gnu': 0.5.48 + '@yuku-parser/binding-linux-arm64-musl': 0.5.48 + '@yuku-parser/binding-linux-x64-gnu': 0.5.48 + '@yuku-parser/binding-linux-x64-musl': 0.5.48 + '@yuku-parser/binding-win32-arm64': 0.5.48 + '@yuku-parser/binding-win32-x64': 0.5.48 + zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3ec1520280..c48f3d7dbe 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -15,13 +15,46 @@ publicHoistPattern: - "prosemirror-*" overrides: "@headlessui/react": "^2.2.4" + # fumadocs-core still declares @shikijs/rehype ^3 while the rest of the + # workspace (code-block, docs, fumadocs-twoslash) is on shiki 4.4.3 - two + # shiki identities in one graph make the docs' rehype transformer types + # clash ("ShikiTransformer | ShikiTransformer"). Force one shiki everywhere. + "shiki": "^4.4.3" + "@shikijs/rehype": "^4.4.3" + "@shikijs/types": "^4.4.3" "@tiptap/core": "^3.29.2" "@tiptap/pm": "^3.29.2" - "vitest": "4.1.7" - "@vitest/runner": "4.1.7" + # vite-plus declares @types/node and jsdom as peers, so every distinct version + # of them in the workspace produces a separate vite-plus (and therefore vitest) + # instance. `vp run test` starts the runner from the workspace root while each + # test file resolves `vite-plus/test` relative to itself, so a package on a + # different instance ends up with a second SnapshotClient and every + # `toMatchSnapshot` fails with "The snapshot state for '...' is not found". + # Pin both so the whole workspace shares one instance. + "@types/node": "^25.6.0" + "jsdom": "^29.0.2" + "vitest": "4.1.10" + "@vitest/runner": "4.1.10" + # @vitest/mocker must stay >=4.1.10: earlier builds only recognize `vitest` + # as the mocks-API source, so `vi.mock` imported from `vite-plus/test` fails + # to hoist ("problems in resolving the mocks API"). 4.1.10 adds vite-plus/test. + "@vitest/mocker": "4.1.10" "@y/y": "14.0.0-rc.23" "@y/prosemirror": "2.0.0-6" "lib0": "1.0.0-rc.22" +packageExtensions: + # `@vitest/ui` is an *optional peer* of vitest, which vite-plus re-exposes as + # an optional peer of its own. Only `tests` declares it (its browser config + # uses the `html` reporter), so it used to be resolved in that one importer + # and missing everywhere else - two `vite-plus` peer keys, two physical + # instances. Declaring it in more importers doesn't help: an importer that + # depends on it directly resolves it *without* vitest attached, so the key + # differs either way. Making it a regular dependency of vite-plus satisfies + # the peer inside vite-plus itself, so it drops out of the peer key entirely + # and every importer shares one instance. + vite-plus: + dependencies: + "@vitest/ui": "4.1.5" allowBuilds: "@parcel/watcher": true "@sentry/cli": true @@ -35,10 +68,13 @@ allowBuilds: workerd: false leveldown: false patchedDependencies: - { "@y/prosemirror@2.0.0-6": patches/@y__prosemirror@2.0.0-6.patch } + { + "@y/prosemirror@2.0.0-6": patches/@y__prosemirror@2.0.0-6.patch, + katex@0.16.47: patches/katex@0.16.47.patch, + } catalog: vite: ^8.0.0 - vite-plus: ^0.1.24 + vite-plus: ^0.2.9 minimumReleaseAgeExclude: - vite-plus - lib0 @@ -51,3 +87,8 @@ minimumReleaseAgeExclude: - "@oxlint-tsgolint/*" - oxfmt - "@oxfmt/*" + +# pdfjs-dist pulls @napi-rs/canvas (native binaries) for Node-side +# rendering, which we never do - pdf.js only runs in the browser suite. +ignoredOptionalDependencies: + - "@napi-rs/canvas" diff --git a/scripts/release.mjs b/scripts/release.mjs index 07cba236de..c72e09a5c9 100755 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -213,4 +213,4 @@ To recover: } } -main(); +void main(); diff --git a/shared/README.md b/shared/README.md index babd002e8f..f45c5f724a 100644 --- a/shared/README.md +++ b/shared/README.md @@ -1 +1,8 @@ This directory contains shared source files. It is not built into a separate package, so consumers should not add it as a dependency in package.json. Instead, use Typescript project references to re-use files from this directory. + +Concretely, a consumer wires this directory up with two things (never a `package.json` dependency): + +- The `@shared/*` path alias, in both `tsconfig.json` (`compilerOptions.paths`) and the bundler config (e.g. the `@shared` alias in `vite.config.ts`). This is the mechanism every `import` uses — e.g. `import { testDocument } from "@shared/testDocument.js"`. +- A TypeScript project reference to `../shared` (`references` in `tsconfig.json`), for build ordering and type resolution. + +The `@blocknote/shared` name in this directory's `package.json` is internal-only: the package is `private` and unbuilt, is never published, and is never imported by that name. See the `@blocknote/tests` package for a reference setup. diff --git a/shared/formatConversionTestUtil.ts b/shared/formatConversionTestUtil.ts index f91ea53b96..1fdab51b45 100644 --- a/shared/formatConversionTestUtil.ts +++ b/shared/formatConversionTestUtil.ts @@ -39,6 +39,7 @@ function partialContentToInlineContent( | PartialTableCell | TableContent | undefined, + inlineContentSchema?: InlineContentSchema, ): | InlineContent[] | TableContent @@ -62,10 +63,22 @@ function partialContentToInlineContent( } else { // custom inline content + // Plain inline content (e.g. inline math) resolves to a bare string, + // not a StyledText array - so keep the string as-is rather than + // treating it as text shorthand. + const config = inlineContentSchema?.[partialContent.type]; + const isPlain = + typeof config === "object" && config.content === "plain"; + return { props: {}, ...partialContent, - content: partialContentToInlineContent(partialContent.content), + content: isPlain + ? (partialContent.content ?? "") + : partialContentToInlineContent( + partialContent.content, + inlineContentSchema, + ), } as any; } }); @@ -78,12 +91,15 @@ function partialContentToInlineContent( rows: content.rows.map((row) => { const cells: any[] = row.cells.map((cell) => { if (!("type" in cell) || cell.type !== "tableCell") { - return partialContentToInlineContent({ - type: "tableCell", - content: cell as any, - }); + return partialContentToInlineContent( + { + type: "tableCell", + content: cell as any, + }, + inlineContentSchema, + ); } - return partialContentToInlineContent(cell); + return partialContentToInlineContent(cell, inlineContentSchema); }); return { @@ -95,7 +111,10 @@ function partialContentToInlineContent( } else if (content?.type === "tableCell") { return { type: "tableCell", - content: partialContentToInlineContent(content.content) as any[], + content: partialContentToInlineContent( + content.content, + inlineContentSchema, + ) as any[], props: { backgroundColor: content.props?.backgroundColor ?? "default", textColor: content.props?.textColor ?? "default", @@ -118,7 +137,11 @@ export function partialBlocksToBlocksForTesting< partialBlocks: Array>, ): Array> { return partialBlocks.map((partialBlock) => - partialBlockToBlockForTesting(schema.blockSchema, partialBlock), + partialBlockToBlockForTesting( + schema.blockSchema, + partialBlock, + schema.inlineContentSchema, + ), ); } @@ -129,6 +152,7 @@ export function partialBlockToBlockForTesting< >( schema: BSchema, partialBlock: PartialBlock, + inlineContentSchema?: InlineContentSchema, ): Block { const contentType: "inline" | "table" | "none" | "plain" = schema[partialBlock.type!].content; @@ -166,7 +190,10 @@ export function partialBlockToBlockForTesting< if (contentType === "inline") { const content = withDefaults.content as InlineContent[] | undefined; - withDefaults.content = partialContentToInlineContent(content) as any; + withDefaults.content = partialContentToInlineContent( + content, + inlineContentSchema, + ) as any; } else if (contentType === "table") { const content = withDefaults.content as TableContent | undefined; withDefaults.content = { @@ -179,16 +206,21 @@ export function partialBlockToBlockForTesting< headerCols: content?.headerCols || undefined, rows: content?.rows.map((row) => ({ - cells: row.cells.map((cell) => partialContentToInlineContent(cell)), + cells: row.cells.map((cell) => + partialContentToInlineContent(cell, inlineContentSchema), + ), })) || [], } as any; } return { ...withDefaults, - content: partialContentToInlineContent(withDefaults.content), + content: partialContentToInlineContent( + withDefaults.content, + inlineContentSchema, + ), children: withDefaults.children.map((c) => { - return partialBlockToBlockForTesting(schema, c); + return partialBlockToBlockForTesting(schema, c, inlineContentSchema); }), } as any; } diff --git a/shared/package.json b/shared/package.json index 66f2d870e4..9f35779aec 100644 --- a/shared/package.json +++ b/shared/package.json @@ -9,22 +9,32 @@ "src" ], "scripts": { - "clean": "tsgo --build --clean" + "clean": "tsc --build --clean" }, "dependencies": { "@blocknote/core": "workspace:^" }, "devDependencies": { "@types/node": "22.13.13", - "typescript": "^5.9.3", - "vite-plus": "catalog:" + "@zip.js/zip.js": "^2.8.8", + "typescript": "^7.0.2", + "vite-plus": "catalog:", + "xml-formatter": "^3.6.7" }, "peerDependencies": { - "image-meta": "^0.2.1" + "@zip.js/zip.js": "^2.8.8", + "image-meta": "^0.2.1", + "xml-formatter": "^3.6.7" }, "peerDependenciesMeta": { + "@zip.js/zip.js": { + "optional": true + }, "image-meta": { "optional": true + }, + "xml-formatter": { + "optional": true } } } diff --git a/shared/testDocument.ts b/shared/testDocument.ts index 754665a59e..1ff87e73d0 100644 --- a/shared/testDocument.ts +++ b/shared/testDocument.ts @@ -1,7 +1,7 @@ import { BlockNoteSchema, - defaultBlockSpecs, createPageBreakBlockSpec, + defaultBlockSpecs, } from "@blocknote/core"; import { partialBlocksToBlocksForTesting } from "./formatConversionTestUtil.js"; @@ -303,3 +303,66 @@ export const testDocument = partialBlocksToBlocksForTesting( }, ], ); + +// Math, inline math & diagram blocks. Their specs live in separate packages +// (`@blocknote/math-block`, `@blocknote/diagram-block`) that `shared` doesn't +// depend on, so they're hand-built (rather than via +// `partialBlocksToBlocksForTesting`) — the exporters only need the block JSON. +// +// These are kept OUT of the base `testDocument` and exposed via +// `testDocumentWithSourceBlocks` below, because `testDocument` is also consumed +// by the suggestion-gallery example / e2e tests, whose editor schema does NOT +// register these block types — seeding them there throws "schema.nodes[...] is +// undefined". Only the exporters (which map the raw block JSON and don't need +// the runtime specs) opt in to the extended document. +const sourceBlocksForTesting = [ + { + id: "math-block", + type: "mathBlock", + props: {}, + content: [{ type: "text", text: "a^2 = \\sqrt{b^2 + c^2}", styles: {} }], + children: [], + }, + { + id: "paragraph-with-inline-math", + type: "paragraph", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + }, + content: [ + { type: "text", text: "Inline math: ", styles: {} }, + { + type: "math", + props: {}, + content: "e^{i\\pi} + 1 = 0", + }, + ], + children: [], + }, + { + id: "diagram-block", + type: "diagram", + props: {}, + content: [ + { + type: "text", + text: "graph TD\n A[Start] --> B[End]", + styles: {}, + }, + ], + children: [], + }, +] as unknown as typeof testDocument; + +/** + * `testDocument` plus the math / inline-math / diagram blocks whose specs live + * in separate packages. Used by the exporter tests (which serialize raw block + * JSON via their default mappings and so don't need the runtime specs). The + * source blocks are appended at the end, so exporter snapshots are unaffected. + */ +export const testDocumentWithSourceBlocks = [ + ...testDocument, + ...sourceBlocksForTesting, +] as typeof testDocument; diff --git a/shared/tsconfig.json b/shared/tsconfig.json index d443d0665d..dfad97cf0a 100644 --- a/shared/tsconfig.json +++ b/shared/tsconfig.json @@ -18,7 +18,8 @@ "emitDeclarationOnly": true, "types": ["node"] }, - "include": ["."], + "include": ["*.ts", "api/**/*.ts", "util/**/*.ts"], + "exclude": ["dist", "assets", "vite.config.ts"], "references": [ { "path": "../packages/core/" diff --git a/shared/util/browserImageTestUtil.ts b/shared/util/browserImageTestUtil.ts new file mode 100644 index 0000000000..5736016ca1 --- /dev/null +++ b/shared/util/browserImageTestUtil.ts @@ -0,0 +1,55 @@ +/** + * Decodes a data URL image and samples its pixels, so browser tests can + * assert generated images actually contain ink rather than being + * valid-but-blank - and that the ink covers the image rather than sitting + * letterboxed in a corner of it (`inkedFractionX`/`inkedFractionY` are the + * fractions of the width/height the inked bounding box spans). Browser-only + * (needs image decoding and a canvas). + */ +export async function decodeAndSample(dataURL: string): Promise<{ + width: number; + height: number; + inkedPixels: number; + inkedFractionX: number; + inkedFractionY: number; +}> { + const image = new Image(); + image.src = dataURL; + await image.decode(); + + const canvas = document.createElement("canvas"); + canvas.width = image.naturalWidth || image.width; + canvas.height = image.naturalHeight || image.height; + const context = canvas.getContext("2d"); + if (!context) { + throw new Error("2D canvas context unavailable for decoding images"); + } + context.drawImage(image, 0, 0); + + const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data; + let inkedPixels = 0; + let minX = canvas.width; + let maxX = -1; + let minY = canvas.height; + let maxY = -1; + for (let i = 3; i < pixels.length; i += 4) { + if (pixels[i] > 0) { + inkedPixels++; + const pixelIndex = (i - 3) / 4; + const x = pixelIndex % canvas.width; + const y = Math.floor(pixelIndex / canvas.width); + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + } + + return { + width: canvas.width, + height: canvas.height, + inkedPixels, + inkedFractionX: maxX < minX ? 0 : (maxX - minX + 1) / canvas.width, + inkedFractionY: maxY < minY ? 0 : (maxY - minY + 1) / canvas.height, + }; +} diff --git a/shared/util/odtTestUtil.ts b/shared/util/odtTestUtil.ts new file mode 100644 index 0000000000..8b794a18d4 --- /dev/null +++ b/shared/util/odtTestUtil.ts @@ -0,0 +1,55 @@ +import { BlobReader, FileEntry, TextWriter, ZipReader } from "@zip.js/zip.js"; +import { expect } from "vite-plus/test"; +import xmlFormat from "xml-formatter"; + +/** + * Verifies an exported ODT document against file snapshots: `styles.xml`, + * `content.xml`, and the embedded objects (the sub-documents that e.g. + * formulas are stored in, as separate `Object N/content.xml` zip entries). + * Tests that don't declare `objects` assert the document embeds none, so + * object payloads can't go unverified. + */ +export async function testODTDocumentAgainstSnapshot( + odt: Blob, + snapshots: { + styles: string; + content: string; + objects?: { snapshot: string; expectedCount: number }; + }, +) { + const zipReader = new ZipReader(new BlobReader(odt)); + const entries = await zipReader.getEntries(); + const stylesXML = entries.find( + (entry) => entry.filename === "styles.xml", + ) as FileEntry; + const contentXML = entries.find( + (entry) => entry.filename === "content.xml", + ) as FileEntry; + + expect(stylesXML).toBeDefined(); + expect(contentXML).toBeDefined(); + await expect( + xmlFormat(await stylesXML.getData(new TextWriter())), + ).toMatchFileSnapshot(snapshots.styles); + await expect( + xmlFormat(await contentXML.getData(new TextWriter())), + ).toMatchFileSnapshot(snapshots.content); + + const objectEntries = entries + .filter((entry) => /^Object \d+\/content\.xml$/.test(entry.filename)) + .sort((a, b) => a.filename.localeCompare(b.filename)) as FileEntry[]; + expect(objectEntries).toHaveLength(snapshots.objects?.expectedCount ?? 0); + + if (snapshots.objects) { + const objectContents = await Promise.all( + objectEntries.map( + async (entry) => + `\n` + + xmlFormat(await entry.getData(new TextWriter())), + ), + ); + await expect(objectContents.join("\n")).toMatchFileSnapshot( + snapshots.objects.snapshot, + ); + } +} diff --git a/shared/vite.config.ts b/shared/vite.config.ts index 817e8db181..224e07b714 100644 --- a/shared/vite.config.ts +++ b/shared/vite.config.ts @@ -4,11 +4,15 @@ export default defineConfig({ run: { tasks: { build: { - command: "tsgo", + command: "tsc", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, ], + // Without declared outputs the cache can't restore `dist/` on a + // cache hit, leaving consumers type-checking against missing or + // stale declarations. + output: ["dist/**", "!dist/**/*.tsbuildinfo"], }, }, }, diff --git a/tests/Dockerfile b/tests/Dockerfile index 532ee39330..f19e9428c4 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -51,4 +51,11 @@ COPY examples ./examples WORKDIR /work/tests # Callers append flags, e.g. `docker run --run` or `--ui --watch --no-open`. -ENTRYPOINT ["pnpm", "exec", "vp", "test", "-c", "vite.config.browser.ts"] +# `vp` is invoked through its bin symlink rather than `pnpm exec`: under CI=1 +# (which the run scripts set) `pnpm exec` first verifies node_modules against the +# lockfile and re-installs when they differ. The install above is filtered +# (`!docs`), so that check always fires — and reinstalling docs pulls +# better-sqlite3, whose prebuilt binary isn't always available; the node-gyp +# fallback then fails since this image has no `make`. The image's deps are +# correct by construction, so there's nothing to verify. +ENTRYPOINT ["node_modules/.bin/vp", "test", "-c", "vite.config.browser.ts"] diff --git a/tests/docker-run.sh b/tests/docker-run.sh index e9fd9eb1c8..c78524166c 100755 --- a/tests/docker-run.sh +++ b/tests/docker-run.sh @@ -27,15 +27,17 @@ done entrypoint_args=("$@") # Auto-rebuild the image if its content hash label doesn't match the current -# repo state. The hash covers every file that affects the installed deps or the -# baked-in examples (lockfile, workspace file, all package.json files, patches, -# and example sources). When the hashes differ the image is rebuilt in place +# repo state. The hash covers every file that affects the image's contents: the +# Dockerfile itself, plus everything it bakes in — the lockfile, workspace file, +# all package.json files, patches, and example sources. When they differ the +# image is rebuilt in place # (Docker's layer cache makes this fast when only a leaf changed). _dep_files() { # Print the sorted list of files that are baked into the image. { echo pnpm-lock.yaml echo pnpm-workspace.yaml + echo tests/Dockerfile find patches examples \( -name node_modules -prune \) -o -type f -print 2>/dev/null find . -name package.json \ -not -path '*/node_modules/*' \ @@ -77,6 +79,9 @@ mounts+=( mounts+=( -v "$PWD/shared/testDocument.ts:/work/shared/testDocument.ts" -v "$PWD/shared/formatConversionTestUtil.ts:/work/shared/formatConversionTestUtil.ts" + -v "$PWD/shared/api:/work/shared/api" + -v "$PWD/shared/util:/work/shared/util" + -v "$PWD/shared/assets:/work/shared/assets" ) # Mount the report dir so the html reporter's output lands on the host instead # of being thrown away with the container. Created on the host first so docker diff --git a/tests/package.json b/tests/package.json index 70dd65a13b..bef2956fcc 100644 --- a/tests/package.json +++ b/tests/package.json @@ -12,6 +12,12 @@ "@blocknote/ariakit": "workspace:^", "@blocknote/core": "workspace:^", "@blocknote/mantine": "workspace:^", + "@blocknote/diagram-block": "workspace:^", + "@blocknote/xl-email-exporter": "workspace:^", + "@blocknote/xl-pdf-exporter": "workspace:^", + "@react-pdf/renderer": "^4.5.1", + "pdfjs-dist": "^4.10.38", + "@blocknote/math-block": "workspace:^", "@blocknote/react": "workspace:^", "@blocknote/shadcn": "workspace:^", "@blocknote/xl-multi-column": "workspace:^", @@ -21,6 +27,7 @@ "@types/node": "^20.19.22", "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", + "@vitest/browser-playwright": "4.1.10", "@vitest/ui": "4.1.5", "@y/protocols": "^1.0.6-rc.1", "@y/y": "^14.0.0-rc.23", diff --git a/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-chromium-linux.png b/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-chromium-linux.png index 8891baa46a..7a73788dd1 100644 Binary files a/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-chromium-linux.png and b/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-chromium-linux.png differ diff --git a/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-firefox-linux.png b/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-firefox-linux.png index bdf0682fd7..19f01795f6 100644 Binary files a/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-firefox-linux.png and b/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-firefox-linux.png differ diff --git a/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-webkit-linux.png b/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-webkit-linux.png index d50b0bf1ab..ac3dee421f 100644 Binary files a/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-webkit-linux.png and b/tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-webkit-linux.png differ diff --git a/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-chromium-linux.png new file mode 100644 index 0000000000..fc78cbb3e6 Binary files /dev/null and b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-chromium-linux.png differ diff --git a/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-firefox-linux.png b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-firefox-linux.png new file mode 100644 index 0000000000..82eacec13e Binary files /dev/null and b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-firefox-linux.png differ diff --git a/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-webkit-linux.png b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-webkit-linux.png new file mode 100644 index 0000000000..04c04c01af Binary files /dev/null and b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-webkit-linux.png differ diff --git a/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-1-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-1-chromium-linux.png new file mode 100644 index 0000000000..20ddc8d64b Binary files /dev/null and b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-1-chromium-linux.png differ diff --git a/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-2-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-2-chromium-linux.png new file mode 100644 index 0000000000..c4f983a18e Binary files /dev/null and b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-2-chromium-linux.png differ diff --git a/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-3-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-3-chromium-linux.png new file mode 100644 index 0000000000..bc81b617ba Binary files /dev/null and b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-3-chromium-linux.png differ diff --git a/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-4-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-4-chromium-linux.png new file mode 100644 index 0000000000..803c5ed6e5 Binary files /dev/null and b/tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-4-chromium-linux.png differ diff --git a/tests/src/end-to-end/exporters/exporterImages.test.tsx b/tests/src/end-to-end/exporters/exporterImages.test.tsx new file mode 100644 index 0000000000..4acd85a13e --- /dev/null +++ b/tests/src/end-to-end/exporters/exporterImages.test.tsx @@ -0,0 +1,236 @@ +import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; +import { diagramBlockMapping as emailDiagramBlockMapping } from "@blocknote/diagram-block/email-exporter"; +import { diagramBlockMapping as pdfDiagramBlockMapping } from "@blocknote/diagram-block/pdf-exporter"; +import { + inlineMathMapping as emailInlineMathMapping, + mathBlockMapping as emailMathBlockMapping, +} from "@blocknote/math-block/email-exporter"; +import { + inlineMathMapping as pdfInlineMathMapping, + mathBlockMapping as pdfMathBlockMapping, +} from "@blocknote/math-block/pdf-exporter"; +import { + ReactEmailExporter, + reactEmailDefaultSchemaMappings, +} from "@blocknote/xl-email-exporter"; +import { + PDFExporter, + pdfDefaultSchemaMappings, +} from "@blocknote/xl-pdf-exporter"; +import { pdf } from "@react-pdf/renderer"; +import { testDocumentWithSourceBlocks } from "@shared/testDocument.js"; +import { decodeAndSample } from "@shared/util/browserImageTestUtil.js"; +import { testResolveFileUrl } from "@shared/util/testFileResolver.js"; +import { afterEach, describe, expect, test } from "vite-plus/test"; +import { browserName } from "../../utils/context.js"; +import { screenshotFull } from "../../utils/screenshotFull.js"; + +// Complete exports of the full shared test document with the default +// mappings in a real browser, where the mappings' `typeof document` checks +// select the built-in image implementations. These are the only tests of +// that composition - the packages' (node) unit suites always take the +// headless side of those checks (or plug in stubs), and the colocated +// `.browser.test` files call the implementations directly, bypassing the +// mappings. A broken default wiring or inverted environment check passes +// every one of those tests and only fails here - while breaking the primary +// real-world path, exporting from the browser. Lives in this package because +// it spans math-block, diagram-block and the exporters, and this is the +// repo's only browser-mode runner. + +// An invalid diagram and an invalid formula, whose typed errors must render +// as placeholders without failing the export. Unlike the packages' node +// suites, this exercises the real error classes through vite's +// bundling/interop of mermaid and mathjax-full - e.g. a mis-resolved +// `TexError` import only breaks here (and in real apps), not in node. +const invalidDiagramBlock = { + id: "invalid-diagram", + type: "diagram", + props: {}, + content: [{ type: "text", text: "not a valid diagram !!", styles: {} }], + children: [], +} as any; +const invalidMathBlock = { + id: "invalid-math", + type: "mathBlock", + props: {}, + // A structural error: MathJax's `noundefined` package renders unknown + // commands as text rather than erroring, so an unknown command wouldn't + // reach the error path. + content: [{ type: "text", text: "\\frac{1}{", styles: {} }], + children: [], +} as any; + +const schema = () => BlockNoteSchema.create({ blockSpecs: defaultBlockSpecs }); + +afterEach(() => { + document.getElementById("export-under-test")?.remove(); +}); + +// Creates the container the export under test is rendered into (removed +// again by the afterEach above). +function createExportFrame(width: string) { + const frame = document.createElement("div"); + frame.id = "export-under-test"; + frame.style.width = width; + frame.style.background = "white"; + document.body.append(frame); + return frame; +} + +describe("email export through a complete exporter in the browser", () => { + test("renders math and diagrams to images", { timeout: 30000 }, async () => { + // The full shared test document, minus the media blocks: the email + // mappings embed media by their (remote) URLs directly, which the + // screenshot below would then try to load over the network. + const emailDocument = [ + ...testDocumentWithSourceBlocks.filter( + (block) => !["image", "video", "audio", "file"].includes(block.type), + ), + invalidDiagramBlock, + invalidMathBlock, + ]; + + const exporter = new ReactEmailExporter(schema(), { + ...reactEmailDefaultSchemaMappings, + blockMapping: { + ...reactEmailDefaultSchemaMappings.blockMapping, + mathBlock: emailMathBlockMapping, + diagram: emailDiagramBlockMapping, + }, + inlineContentMapping: { + ...reactEmailDefaultSchemaMappings.inlineContentMapping, + math: emailInlineMathMapping, + }, + } as any); + + const html = await exporter.toReactEmailDocument(emailDocument as any); + + // Three generated images: block math (rasterized to PNG in the + // browser), inline math (always SVG), and the valid diagram (PNG). The + // invalid diagram renders the error placeholder instead - and doesn't + // fail the export. + // Decodes the HTML-escaped attribute value; `&` must be decoded + // last - decoding it first would double-unescape sequences like + // `&#x27;` (an escaped literal `'`) into `'`. + const srcs = [...html.matchAll(/]*src="(data:[^"]+)"/g)].map( + (match) => match[1].replaceAll("'", "'").replaceAll("&", "&"), + ); + expect(srcs).toHaveLength(3); + expect(srcs[0]).toMatch(/^data:image\/png/); + expect(srcs[1]).toMatch(/^data:image\/svg\+xml/); + expect(srcs[2]).toMatch(/^data:image\/png/); + for (const src of srcs) { + expect((await decodeAndSample(src)).inkedPixels).toBeGreaterThan(0); + } + expect(html).toContain("Invalid diagram"); + expect(html).toContain("Invalid formula"); + + // Visual regression of the exported email as a client would show it, + // rendered at 600px (typical email client width). + const frame = createExportFrame("600px"); + frame.innerHTML = html; + // Wait until every image is ready to paint - a screenshot taken while a + // data: URL is still decoding captures a gap (and unloaded images throw + // off the height measurement in screenshotFull). + await Promise.all( + [...frame.querySelectorAll("img")].map((img) => img.decode()), + ); + await screenshotFull(frame, "email-export"); + }); +}); + +describe("pdf export through a complete exporter in the browser", () => { + // Chromium only: the produced PDF is the same file everywhere (react-pdf + // lays it out from bundled fonts, not browser rendering), so per-browser + // runs would only re-test pdf.js's rasterizer at 3x the suite cost. + test.skipIf(browserName !== "chromium")( + "renders math and diagrams to images in the produced PDF", + { timeout: 60000 }, + async () => { + const mappings = { + ...pdfDefaultSchemaMappings, + blockMapping: { + ...pdfDefaultSchemaMappings.blockMapping, + mathBlock: pdfMathBlockMapping, + diagram: pdfDiagramBlockMapping, + }, + inlineContentMapping: { + ...pdfDefaultSchemaMappings.inlineContentMapping, + math: pdfInlineMathMapping, + }, + }; + // The full shared test document: unlike the email mappings, the PDF + // exporter fetches media through `resolveFileUrl`, so the test + // resolver keeps it deterministic and offline. + const exporter = new PDFExporter(schema(), mappings as any, { + resolveFileUrl: testResolveFileUrl, + }); + + const transformed = await exporter.toReactPDFDocument([ + ...testDocumentWithSourceBlocks, + invalidDiagramBlock, + invalidMathBlock, + ] as any); + + // The element tree carries the inline math as an image with + // react-pdf's async (rasterizing) src function, and the diagram as a + // rendered PNG. + const images: any[] = []; + const collectImages = (node: any) => { + if (!node || typeof node !== "object") { + return; + } + if (Array.isArray(node)) { + node.forEach(collectImages); + return; + } + if (node.type === "IMAGE") { + images.push(node); + } + collectImages(node.props?.children); + }; + collectImages(transformed); + expect(images.some((i) => typeof i.props.src === "function")).toBe(true); + expect( + images.some((i) => String(i.props.src).startsWith("data:image/png")), + ).toBe(true); + + // Produce the actual file - this runs react-pdf's asset resolution, + // which invokes the inline math's rasterizing src function. + const blob = await pdf(transformed as any).toBlob(); + const bytes = new Uint8Array(await blob.arrayBuffer()); + expect(new TextDecoder().decode(bytes.slice(0, 5))).toBe("%PDF-"); + + // Render the produced PDF's pages with pdf.js (pure JS - the reason + // the old Node-side attempt at this failed was native canvas + // dependencies, which a real browser doesn't need) and screenshot + // them, stacked, as a visual regression of the actual export. + const pdfjs = await import("pdfjs-dist"); + const workerUrl = ( + await import("pdfjs-dist/build/pdf.worker.min.mjs?url" as string) + ).default; + pdfjs.GlobalWorkerOptions.workerSrc = workerUrl; + + const parsed = await pdfjs.getDocument({ data: bytes }).promise; + // The test document contains a page break. + expect(parsed.numPages).toBeGreaterThanOrEqual(2); + + // Screenshot each page as its own full-resolution baseline. + const frame = createExportFrame("fit-content"); + for (let n = 1; n <= parsed.numPages; n++) { + const pdfPage = await parsed.getPage(n); + const viewport = pdfPage.getViewport({ scale: 1 }); + const canvas = document.createElement("canvas"); + canvas.width = viewport.width; + canvas.height = viewport.height; + canvas.style.display = "block"; + await pdfPage.render({ + canvasContext: canvas.getContext("2d")!, + viewport, + } as any).promise; + frame.replaceChildren(canvas); + await screenshotFull(frame, `pdf-export-page-${n}`); + } + }, + ); +}); diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-chromium-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-chromium-linux.png index 32c5a65f99..8145737c48 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-chromium-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-chromium-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-firefox-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-firefox-linux.png index 09682f8601..d391b8fb0e 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-firefox-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-firefox-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-webkit-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-webkit-linux.png index cbed364961..36d68026d2 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-webkit-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-webkit-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-chromium-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-chromium-linux.png index 4fddbd3b9b..150757d98d 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-chromium-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-chromium-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-firefox-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-firefox-linux.png index 4ecbbb860b..ca81076c86 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-firefox-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-firefox-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-webkit-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-webkit-linux.png index 0f4251b10a..d03b263d38 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-webkit-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/embed-image-webkit-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-chromium-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-chromium-linux.png index f908dcbbfd..9d9d4ac87c 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-chromium-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-chromium-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-firefox-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-firefox-linux.png index f5e64ec07f..b8244698ea 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-firefox-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-firefox-linux.png differ diff --git a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-webkit-linux.png b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-webkit-linux.png index 76c69d701b..14caaec633 100644 Binary files a/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-webkit-linux.png and b/tests/src/end-to-end/images/__screenshots__/images.test.tsx/resize-image-webkit-linux.png differ diff --git a/tests/src/end-to-end/images/images.test.tsx b/tests/src/end-to-end/images/images.test.tsx index ae49808c10..67ded55285 100644 --- a/tests/src/end-to-end/images/images.test.tsx +++ b/tests/src/end-to-end/images/images.test.tsx @@ -130,6 +130,26 @@ describe("Check Image Block and Toolbar functionality", () => { await compareDocToSnapshot("deleteImage"); }); + test("Should show formatting toolbar when image block is selected", async () => { + await render(); + await waitForSelector(EDITOR_SELECTOR); + await focusOnEditor(); + await executeSlashCommand("image"); + + await userEvent.click(await waitForSelector(`[data-test="embed-tab"]`)); + await userEvent.click(await waitForSelector(`[data-test="embed-input"]`)); + await userEvent.keyboard(IMAGE_EMBED_URL); + await userEvent.click( + await waitForSelector(`[data-test="embed-input-button"]`), + ); + await waitForSelector(`img[src="${IMAGE_EMBED_URL}"]`); + await sleep(500); + + await userEvent.click(await waitForSelector(`img`)); + + const toolbar = await waitForSelector(".bn-formatting-toolbar"); + await expectElement(toolbar).toBeVisible(); + }); test("Should open file panel but not formatting toolbar when inserting image with no trailing block", async () => { await render(); await waitForSelector(EDITOR_SELECTOR); diff --git a/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-chromium-linux.png b/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-chromium-linux.png new file mode 100644 index 0000000000..c349554d14 Binary files /dev/null and b/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-chromium-linux.png differ diff --git a/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-firefox-linux.png b/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-firefox-linux.png new file mode 100644 index 0000000000..4b2491fa80 Binary files /dev/null and b/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-firefox-linux.png differ diff --git a/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-webkit-linux.png b/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-webkit-linux.png new file mode 100644 index 0000000000..e964772e39 Binary files /dev/null and b/tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-webkit-linux.png differ diff --git a/tests/src/end-to-end/screenshots/screenshotFull.test.tsx b/tests/src/end-to-end/screenshots/screenshotFull.test.tsx new file mode 100644 index 0000000000..b2eb8b759f --- /dev/null +++ b/tests/src/end-to-end/screenshots/screenshotFull.test.tsx @@ -0,0 +1,36 @@ +import { describe, test } from "vite-plus/test"; + +import { screenshotFull } from "../../utils/screenshotFull.js"; + +// Guards the capture utility itself, on synthetic content: screenshotFull +// depends on harness DOM internals (see its doc comment), and a harness +// update that breaks them would otherwise only surface as confusing +// failures in the feature tests using it. Numbered stripes make the failure +// modes obvious in the diff: a plain capture blanks out below ~712px, a +// viewport-only capture comes out downscaled to ~0.14x - the baseline +// proves the full 600x2000 render. +describe("screenshotFull", () => { + test( + "captures a tall element completely at full resolution", + { timeout: 30000 }, + async () => { + const frame = document.createElement("div"); + frame.style.width = "600px"; + frame.style.background = "white"; + for (let i = 0; i < 40; i++) { + const stripe = document.createElement("div"); + stripe.textContent = `stripe ${i} - starts at y = ${i * 50}px`; + stripe.style.height = "50px"; + stripe.style.font = "20px sans-serif"; + stripe.style.background = i % 2 ? "#e3f2fd" : "#fff3e0"; + frame.append(stripe); + } + document.body.append(frame); + try { + await screenshotFull(frame, "screenshot-full-tall-element"); + } finally { + frame.remove(); + } + }, + ); +}); diff --git a/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-chromium-linux.png b/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-chromium-linux.png index 88b6f539a0..711909236c 100644 Binary files a/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-chromium-linux.png and b/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-chromium-linux.png differ diff --git a/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-firefox-linux.png b/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-firefox-linux.png index 53cd85457d..6953a2e082 100644 Binary files a/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-firefox-linux.png and b/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-firefox-linux.png differ diff --git a/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-webkit-linux.png b/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-webkit-linux.png index d66e677cec..f76179dd69 100644 Binary files a/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-webkit-linux.png and b/tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-webkit-linux.png differ diff --git a/tests/src/end-to-end/static/static.test.tsx b/tests/src/end-to-end/static/static.test.tsx index a9d20e5db6..544b0c2fa4 100644 --- a/tests/src/end-to-end/static/static.test.tsx +++ b/tests/src/end-to-end/static/static.test.tsx @@ -47,6 +47,10 @@ describe("Check static rendering", () => { "static-rendering-equality", { comparatorOptions: { allowedMismatchedPixels: 200 }, + // scale: "css" is load-bearing: with the harness's fit-to-window + // transform on the tester iframe, Playwright's css/device capture + // paths rasterize slightly differently, and dropping it pushes + // the diff past the pixel budget (empirically, chromium). screenshotOptions: { scale: "css", mask: masks() }, }, ); diff --git a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-chromium-linux.png b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-chromium-linux.png index 41c579ae20..995dbe861b 100644 Binary files a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-chromium-linux.png and b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-chromium-linux.png differ diff --git a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-firefox-linux.png b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-firefox-linux.png index 8d8dc56fa7..73f5461589 100644 Binary files a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-firefox-linux.png and b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-firefox-linux.png differ diff --git a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-webkit-linux.png b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-webkit-linux.png index bde04467ae..f2af988f99 100644 Binary files a/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-webkit-linux.png and b/tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-webkit-linux.png differ diff --git a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-chromium-linux.png b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-chromium-linux.png index 33ea5ad713..7dc690ad9e 100644 Binary files a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-chromium-linux.png and b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-chromium-linux.png differ diff --git a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-firefox-linux.png b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-firefox-linux.png index 85c0a441c1..57717574e2 100644 Binary files a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-firefox-linux.png and b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-firefox-linux.png differ diff --git a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-webkit-linux.png b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-webkit-linux.png index c85c2e8471..18dfb205e8 100644 Binary files a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-webkit-linux.png and b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-source-webkit-linux.png differ diff --git a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-chromium-linux.png b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-chromium-linux.png index 7a85f71134..71dbb3fe33 100644 Binary files a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-chromium-linux.png and b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-chromium-linux.png differ diff --git a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-firefox-linux.png b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-firefox-linux.png index f4022db958..1e43f2ed63 100644 Binary files a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-firefox-linux.png and b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-firefox-linux.png differ diff --git a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-webkit-linux.png b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-webkit-linux.png index 29be5e0a4a..b6c5e22092 100644 Binary files a/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-webkit-linux.png and b/tests/src/end-to-end/y-prosemirror/__screenshots__/propChanges.test.tsx/prop-change-image-width-webkit-linux.png differ diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/basicBlocks.html b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/basicBlocks.html index b59aa81d46..0d6a42d952 100644 --- a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/basicBlocks.html +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/basicBlocks.html @@ -22,7 +22,7 @@

Heading 1

-  console.log("Hello World");
+  console.log("Hello World");
 
diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocks.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocks.md index 59e94f2356..378193ad13 100644 --- a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocks.md +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocks.md @@ -8,7 +8,7 @@ Paragraph 1 * [ ] Check List Item 1 * Toggle List Item 1 -```text +```javascript console.log("Hello World"); ``` diff --git a/tests/src/unit/core/createTestEditor.ts b/tests/src/unit/core/createTestEditor.ts index aa804ffdd6..26c9324f91 100644 --- a/tests/src/unit/core/createTestEditor.ts +++ b/tests/src/unit/core/createTestEditor.ts @@ -26,11 +26,16 @@ export const createTestEditor = < schema: schema.extend({ blockSpecs: { codeBlock: createCodeBlockSpec({ + defaultLanguage: "javascript", supportedLanguages: { javascript: { name: "JavaScript", aliases: ["js"], }, + typescript: { + name: "TypeScript", + aliases: ["ts"], + }, python: { name: "Python", aliases: ["py"], diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html index bf789c1a7d..553b646dbc 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html @@ -1,14 +1,11 @@
-
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/defaultLanguage.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/defaultLanguage.html index 861d648003..b5b31e8062 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/defaultLanguage.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/defaultLanguage.html @@ -5,6 +5,7 @@
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html index ce97dbaaac..8aac992379 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html @@ -5,6 +5,7 @@
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/python.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/python.html index 1223a7d041..0d65939e44 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/python.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/python.html @@ -9,6 +9,7 @@
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html index 4376ebf7f1..b87505e81f 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html @@ -70,14 +70,11 @@

Section 1

-
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html index a7db81b06b..ea6a3e8a21 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html @@ -1,4 +1,4 @@ -
+
   const hello = 'world';
 console.log(hello);
 
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/defaultLanguage.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/defaultLanguage.html
index c5939c1b5e..d9a00bc084 100644
--- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/defaultLanguage.html
+++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/defaultLanguage.html
@@ -1,3 +1,3 @@
 
-  console.log('Hello, world!');
+  console.log('Hello, world!');
 
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/empty.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/empty.html index 9bbe62c374..f2e39bcbc7 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/empty.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/empty.html @@ -1,3 +1,3 @@
-  
+  
 
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html index 421d420c08..47caad18e7 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html @@ -25,6 +25,6 @@

Section 1


A notable quote
-
+
   const x = 42;
 
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/defaultLanguage.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/defaultLanguage.md index f5b118ae95..eca2b94e33 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/defaultLanguage.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/defaultLanguage.md @@ -1,3 +1,3 @@ -```text +```javascript console.log('Hello, world!'); ``` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/empty.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/empty.md index b5c9416ec5..04144d877f 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/empty.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/codeBlock/empty.md @@ -1,2 +1,2 @@ -```text +```javascript ``` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/defaultLanguage.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/defaultLanguage.json index e25d8ad37a..cb4329b686 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/defaultLanguage.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/defaultLanguage.json @@ -6,7 +6,7 @@ "content": [ { "attrs": { - "language": "text", + "language": "javascript", }, "content": [ { diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/empty.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/empty.json index fc526a8406..a278421822 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/empty.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/codeBlock/empty.json @@ -6,7 +6,7 @@ "content": [ { "attrs": { - "language": "text", + "language": "javascript", }, "type": "codeBlock", }, diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json index 0ede1c2000..aae0b9afdc 100644 --- a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json @@ -117,7 +117,7 @@ const y = '```triple backticks```';", ], "id": "5", "props": { - "language": "text", + "language": "javascript", }, "type": "codeBlock", }, diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocks.json index f9bd791440..f4a808da84 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocks.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocks.json @@ -10,7 +10,7 @@ ], "id": "1", "props": { - "language": "text", + "language": "javascript", }, "type": "codeBlock", }, diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksMultiLine.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksMultiLine.json index 6cb94084f1..4d8c20bb1a 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksMultiLine.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksMultiLine.json @@ -12,7 +12,7 @@ console.log("Third Line")", ], "id": "1", "props": { - "language": "text", + "language": "javascript", }, "type": "codeBlock", }, diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksNestedHTML.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksNestedHTML.json index e6480d3f5b..8a025ee12a 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksNestedHTML.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/codeBlocksNestedHTML.json @@ -27,7 +27,7 @@ line two", ], "id": "2", "props": { - "language": "text", + "language": "javascript", }, "type": "codeBlock", }, diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json index cf59869a6f..fc0a925232 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json @@ -10,7 +10,7 @@ ], "id": "1", "props": { - "language": "text", + "language": "javascript", }, "type": "codeBlock", }, diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json index 1a656bd726..a3da1d66c6 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json @@ -10,7 +10,7 @@ ], "id": "1", "props": { - "language": "text", + "language": "javascript", }, "type": "codeBlock", }, diff --git a/tests/src/unit/core/schema/__snapshots__/blocks.json b/tests/src/unit/core/schema/__snapshots__/blocks.json index f0c513b02f..ee48987244 100644 --- a/tests/src/unit/core/schema/__snapshots__/blocks.json +++ b/tests/src/unit/core/schema/__snapshots__/blocks.json @@ -121,19 +121,19 @@ "content": "plain", "propSchema": { "language": { - "default": "text", + "default": "javascript", }, }, "type": "codeBlock", }, "extensions": [ [Function], - [Function], ], "implementation": { "meta": { "code": true, "defining": true, + "highlight": [Function], "isolating": false, }, "node": null, diff --git a/tests/src/unit/core/schema/__snapshots__/inlinecontent.json b/tests/src/unit/core/schema/__snapshots__/inlinecontent.json index c682709782..6013f4791c 100644 --- a/tests/src/unit/core/schema/__snapshots__/inlinecontent.json +++ b/tests/src/unit/core/schema/__snapshots__/inlinecontent.json @@ -13,6 +13,7 @@ }, "type": "mention", }, + "extensions": undefined, "implementation": { "node": null, "parse": [Function], @@ -26,6 +27,7 @@ "propSchema": {}, "type": "tag", }, + "extensions": undefined, "implementation": { "node": null, "parse": [Function], diff --git a/tests/src/unit/react/clipboard/copy/__snapshots__/text/html/math.html b/tests/src/unit/react/clipboard/copy/__snapshots__/text/html/math.html new file mode 100644 index 0000000000..369ffd1dd8 --- /dev/null +++ b/tests/src/unit/react/clipboard/copy/__snapshots__/text/html/math.html @@ -0,0 +1,32 @@ +The identity + + + + + + e + + i + π + + + + + 1 + = + 0 + + e^{i\pi} + 1 = 0 + + + +is elegant. \ No newline at end of file diff --git a/tests/src/unit/react/clipboard/copy/__snapshots__/text/html/mathBlock.html b/tests/src/unit/react/clipboard/copy/__snapshots__/text/html/mathBlock.html new file mode 100644 index 0000000000..3453e9a620 --- /dev/null +++ b/tests/src/unit/react/clipboard/copy/__snapshots__/text/html/mathBlock.html @@ -0,0 +1,25 @@ + + + + + a + 2 + + + + + b + 2 + + = + + c + 2 + + + a^2 + b^2 = c^2 + + \ No newline at end of file diff --git a/tests/src/unit/react/clipboard/copy/copyTestInstances.ts b/tests/src/unit/react/clipboard/copy/copyTestInstances.ts new file mode 100644 index 0000000000..5e086202d8 --- /dev/null +++ b/tests/src/unit/react/clipboard/copy/copyTestInstances.ts @@ -0,0 +1,71 @@ +import { NodeSelection, TextSelection } from "@tiptap/pm/state"; + +import { CopyTestCase } from "../../../shared/clipboard/copy/copyTestCase.js"; +import { testCopyHTML } from "../../../shared/clipboard/copy/copyTestExecutors.js"; +import { getPosOfTextNode } from "../../../shared/testUtil.js"; +import { TestInstance } from "../../../types.js"; +import { + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema, +} from "../../testSchema.js"; + +export const copyTestInstancesHTML: TestInstance< + CopyTestCase, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = [ + { + testCase: { + name: "mathBlock", + document: [ + { + type: "mathBlock", + content: "a^2 + b^2 = c^2", + }, + ], + getCopySelection: (doc) => { + let startPos: number | undefined = undefined; + + doc.descendants((node, pos) => { + if (node.type.name === "mathBlock") { + startPos = pos; + } + }); + + if (startPos === undefined) { + throw new Error("Math node not found."); + } + + return NodeSelection.create(doc, startPos); + }, + }, + executeTest: testCopyHTML, + }, + { + testCase: { + name: "math", + document: [ + { + type: "paragraph", + content: [ + "The identity ", + { + type: "math", + content: "e^{i\\pi} + 1 = 0", + } as const, + " is elegant.", + ], + }, + ], + getCopySelection: (doc) => { + const startPos = getPosOfTextNode(doc, "The identity "); + const endPos = getPosOfTextNode(doc, " is elegant.", true); + + return TextSelection.create(doc, startPos, endPos); + }, + }, + executeTest: testCopyHTML, + }, +]; diff --git a/tests/src/unit/react/clipboard/copy/runTests.test.ts b/tests/src/unit/react/clipboard/copy/runTests.test.ts new file mode 100644 index 0000000000..f26b0e61e3 --- /dev/null +++ b/tests/src/unit/react/clipboard/copy/runTests.test.ts @@ -0,0 +1,15 @@ +import { describe, it } from "vite-plus/test"; + +import { setupTestEditor } from "../../setupTestEditor.js"; +import { testSchema } from "../../testSchema.js"; +import { copyTestInstancesHTML } from "./copyTestInstances.js"; + +describe("React copy tests (HTML)", () => { + const getEditor = setupTestEditor(testSchema); + + for (const { testCase, executeTest } of copyTestInstancesHTML) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); diff --git a/tests/src/unit/react/clipboard/copyPasteEquality/copyPasteEqualityTestInstances.ts b/tests/src/unit/react/clipboard/copyPasteEquality/copyPasteEqualityTestInstances.ts new file mode 100644 index 0000000000..8f9bc636ab --- /dev/null +++ b/tests/src/unit/react/clipboard/copyPasteEquality/copyPasteEqualityTestInstances.ts @@ -0,0 +1,34 @@ +import { CopyPasteEqualityTestCase } from "../../../shared/clipboard/copyPasteEquality/copyPasteEqualityTestCase.js"; +import { testCopyPasteEquality } from "../../../shared/clipboard/copyPasteEquality/copyPasteEqualityTestExecutors.js"; +import { TestInstance } from "../../../types.js"; +import { + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema, +} from "../../testSchema.js"; +import { copyTestInstancesHTML } from "../copy/copyTestInstances.js"; + +// NOTE: Only the block is covered in these tests. Inline `` is foreign (non-HTML) content, +// and ProseMirror's clipboard parser in the jsdom unit environment doesn't run the inline +// content's `parseContent` for it, so pasting the copied inline math doubles its source here. It +// round-trips correctly in a real browser and should be covered by the browser-mode e2e suite +// instead. +export const copyPasteEqualityTestInstances: TestInstance< + CopyPasteEqualityTestCase< + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema + >, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = copyTestInstancesHTML + .filter(({ testCase }) => testCase.name === "mathBlock") + .map(({ testCase }) => ({ + testCase: { + name: testCase.name, + document: testCase.document, + getCopyAndPasteSelection: testCase.getCopySelection, + }, + executeTest: testCopyPasteEquality, + })); diff --git a/tests/src/unit/react/clipboard/copyPasteEquality/runTests.test.ts b/tests/src/unit/react/clipboard/copyPasteEquality/runTests.test.ts new file mode 100644 index 0000000000..dfb3241a05 --- /dev/null +++ b/tests/src/unit/react/clipboard/copyPasteEquality/runTests.test.ts @@ -0,0 +1,15 @@ +import { describe, it } from "vite-plus/test"; + +import { setupTestEditor } from "../../setupTestEditor.js"; +import { testSchema } from "../../testSchema.js"; +import { copyPasteEqualityTestInstances } from "./copyPasteEqualityTestInstances.js"; + +describe("React copy/paste equality tests", () => { + const getEditor = setupTestEditor(testSchema); + + for (const { testCase, executeTest } of copyPasteEqualityTestInstances) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/diagram/basic.html b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/diagram/basic.html new file mode 100644 index 0000000000..dc83c065d9 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/diagram/basic.html @@ -0,0 +1,39 @@ +
+
+
+
+
+
+
+
+ +
+ +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/math/basic.html b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/math/basic.html new file mode 100644 index 0000000000..a514081671 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/math/basic.html @@ -0,0 +1,124 @@ +
+
+
+
+

+ The identity + + + + + + + + + + + e + + i + π + + + + + 1 + = + 0 + + e^{i\pi} + 1 = 0 + + + + + + + +

+
+ +
+ +
+
+ +
+ + + is elegant. +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/mathBlock/basic.html b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/mathBlock/basic.html new file mode 100644 index 0000000000..e59cc316e6 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/mathBlock/basic.html @@ -0,0 +1,152 @@ +
+
+
+
+
+
+ + + + + + + + + a + 2 + + + + + b + 2 + + = + + c + 2 + + + a^2 + b^2 = c^2 + + + + + + + +
+
+
+ +
+ +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/html/diagram/basic.html b/tests/src/unit/react/formatConversion/export/__snapshots__/html/diagram/basic.html new file mode 100644 index 0000000000..eb3b214fd7 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/html/diagram/basic.html @@ -0,0 +1,4 @@ +
+  graph TD
+  A[Start] --> B[End]
+
\ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/html/math/basic.html b/tests/src/unit/react/formatConversion/export/__snapshots__/html/math/basic.html new file mode 100644 index 0000000000..7e939f980c --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/html/math/basic.html @@ -0,0 +1,34 @@ +

+ The identity + + + + + + e + + i + π + + + + + 1 + = + 0 + + e^{i\pi} + 1 = 0 + + + + is elegant. +

\ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/html/mathBlock/basic.html b/tests/src/unit/react/formatConversion/export/__snapshots__/html/mathBlock/basic.html new file mode 100644 index 0000000000..3453e9a620 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/html/mathBlock/basic.html @@ -0,0 +1,25 @@ + + + + + a + 2 + + + + + b + 2 + + = + + c + 2 + + + a^2 + b^2 = c^2 + + \ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/codeBlock/nested.md b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/codeBlock/nested.md new file mode 100644 index 0000000000..75edc69f51 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/codeBlock/nested.md @@ -0,0 +1,6 @@ +* The snippet: + ```javascript + const a = 1; + + const b = 2; + ``` diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/diagram/basic.md b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/diagram/basic.md new file mode 100644 index 0000000000..0c7bf63597 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/diagram/basic.md @@ -0,0 +1,4 @@ +```mermaid +graph TD + A[Start] --> B[End] +``` diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/inlineMath/basic.md b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/inlineMath/basic.md new file mode 100644 index 0000000000..af6c63b601 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/inlineMath/basic.md @@ -0,0 +1 @@ +The identity $e^{i\pi} + 1 = 0$ is elegant. diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/basic.md b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/basic.md new file mode 100644 index 0000000000..d029e7e869 --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/basic.md @@ -0,0 +1,3 @@ +$$ +a^2 + b^2 = c^2 +$$ diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/nested.md b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/nested.md new file mode 100644 index 0000000000..5b85d724fe --- /dev/null +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/nested.md @@ -0,0 +1,5 @@ +* The theorem: + $$ + a^2 + + b^2 = c^2 + $$ diff --git a/tests/src/unit/react/formatConversion/export/exportTestInstances.ts b/tests/src/unit/react/formatConversion/export/exportTestInstances.ts index d822928fb6..8f3ab89bf9 100644 --- a/tests/src/unit/react/formatConversion/export/exportTestInstances.ts +++ b/tests/src/unit/react/formatConversion/export/exportTestInstances.ts @@ -2,6 +2,7 @@ import { ExportTestCase } from "../../../shared/formatConversion/export/exportTe import { testExportBlockNoteHTML, testExportHTML, + testExportMarkdown, } from "../../../shared/formatConversion/export/exportTestExecutors.js"; import { TestInstance } from "../../../types.js"; import { @@ -470,6 +471,49 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + testCase: { + name: "mathBlock/basic", + content: [ + { + type: "mathBlock", + content: "a^2 + b^2 = c^2", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "math/basic", + content: [ + { + type: "paragraph", + content: [ + "The identity ", + { + type: "math", + content: "e^{i\\pi} + 1 = 0", + } as const, + " is elegant.", + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "diagram/basic", + content: [ + { + type: "diagram", + content: "graph TD\n A[Start] --> B[End]", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, ]; export const exportTestInstancesHTML: TestInstance< @@ -481,3 +525,99 @@ export const exportTestInstancesHTML: TestInstance< testCase, executeTest: testExportHTML, })); + +// Markdown export runs the external HTML through the markdown serializer: +// the diagram's fenced-code representation should come out as a ```mermaid +// fence, and math's MathML (via its LaTeX source annotation) as $$/$ spans. +export const exportTestInstancesMarkdown: TestInstance< + ExportTestCase, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = [ + { + testCase: { + name: "diagram/basic", + content: [ + { + type: "diagram", + content: "graph TD\n A[Start] --> B[End]", + }, + ], + }, + executeTest: testExportMarkdown, + }, + { + testCase: { + name: "math/basic", + content: [ + { + type: "mathBlock", + content: "a^2 + b^2 = c^2", + }, + ], + }, + executeTest: testExportMarkdown, + }, + { + // Nested multi-line blocks must indent every line (including the closing + // delimiter) - an unindented line would end the list item. Toggle items + // are the case where this occurs: external HTML flattens other list + // items' non-list children to siblings, but keeps toggle children + // nested. + testCase: { + name: "math/nested", + content: [ + { + type: "toggleListItem", + content: "The theorem:", + children: [ + { + type: "mathBlock", + content: "a^2 +\nb^2 = c^2", + }, + ], + }, + ], + }, + executeTest: testExportMarkdown, + }, + { + testCase: { + name: "codeBlock/nested", + content: [ + { + type: "toggleListItem", + content: "The snippet:", + children: [ + { + type: "codeBlock", + props: { language: "javascript" }, + content: "const a = 1;\n\nconst b = 2;", + }, + ], + }, + ], + }, + executeTest: testExportMarkdown, + }, + { + testCase: { + name: "inlineMath/basic", + content: [ + { + type: "paragraph", + content: [ + "The identity ", + { + type: "math", + content: "e^{i\\pi} + 1 = 0", + } as const, + " is elegant.", + ], + }, + ], + }, + executeTest: testExportMarkdown, + }, +]; diff --git a/tests/src/unit/react/formatConversion/export/runTests.test.ts b/tests/src/unit/react/formatConversion/export/runTests.test.ts index 514171d525..4a63a80858 100644 --- a/tests/src/unit/react/formatConversion/export/runTests.test.ts +++ b/tests/src/unit/react/formatConversion/export/runTests.test.ts @@ -5,6 +5,7 @@ import { testSchema } from "../../testSchema.js"; import { exportTestInstancesBlockNoteHTML, exportTestInstancesHTML, + exportTestInstancesMarkdown, } from "./exportTestInstances.js"; describe("React export tests (BlockNote HTML)", () => { @@ -26,3 +27,13 @@ describe("React export tests (HTML)", () => { }); } }); + +describe("React export tests (Markdown)", () => { + const getEditor = setupTestEditor(testSchema); + + for (const { testCase, executeTest } of exportTestInstancesMarkdown) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); diff --git a/tests/src/unit/react/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts b/tests/src/unit/react/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts index 2deca013d9..29364b8dbe 100644 --- a/tests/src/unit/react/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts +++ b/tests/src/unit/react/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts @@ -20,10 +20,12 @@ export const exportParseEqualityTestInstancesBlockNoteHTML: TestInstance< TestBlockSchema, TestInlineContentSchema, TestStyleSchema ->[] = exportTestInstancesBlockNoteHTML.map(({ testCase }) => ({ - testCase, - executeTest: testExportParseEqualityBlockNoteHTML, -})); +>[] = exportTestInstancesBlockNoteHTML + .filter(({ testCase }) => testCase.name !== "math/basic") + .map(({ testCase }) => ({ + testCase, + executeTest: testExportParseEqualityBlockNoteHTML, + })); export const exportParseEqualityTestInstancesNodes: TestInstance< ExportParseEqualityTestCase< @@ -34,7 +36,7 @@ export const exportParseEqualityTestInstancesNodes: TestInstance< TestBlockSchema, TestInlineContentSchema, TestStyleSchema ->[] = exportParseEqualityTestInstancesBlockNoteHTML.map(({ testCase }) => ({ +>[] = exportTestInstancesBlockNoteHTML.map(({ testCase }) => ({ testCase, executeTest: testExportParseEqualityNodes, })); diff --git a/tests/src/unit/react/formatConversion/parse/__snapshots__/html/diagramBlock.json b/tests/src/unit/react/formatConversion/parse/__snapshots__/html/diagramBlock.json new file mode 100644 index 0000000000..4c8f430455 --- /dev/null +++ b/tests/src/unit/react/formatConversion/parse/__snapshots__/html/diagramBlock.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "graph TD + A[Start] --> B[End]", + "type": "text", + }, + ], + "id": "1", + "props": {}, + "type": "diagram", + }, +] \ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/parse/__snapshots__/html/mathBlock.json b/tests/src/unit/react/formatConversion/parse/__snapshots__/html/mathBlock.json new file mode 100644 index 0000000000..7b1b5a816d --- /dev/null +++ b/tests/src/unit/react/formatConversion/parse/__snapshots__/html/mathBlock.json @@ -0,0 +1,15 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "a^2 + b^2 = c^2", + "type": "text", + }, + ], + "id": "1", + "props": {}, + "type": "mathBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/parse/__snapshots__/markdown/codeBlockNotDiagram.json b/tests/src/unit/react/formatConversion/parse/__snapshots__/markdown/codeBlockNotDiagram.json new file mode 100644 index 0000000000..d48d7ebb30 --- /dev/null +++ b/tests/src/unit/react/formatConversion/parse/__snapshots__/markdown/codeBlockNotDiagram.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "console.log('hi');", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/parse/__snapshots__/markdown/diagramBlock.json b/tests/src/unit/react/formatConversion/parse/__snapshots__/markdown/diagramBlock.json new file mode 100644 index 0000000000..4c8f430455 --- /dev/null +++ b/tests/src/unit/react/formatConversion/parse/__snapshots__/markdown/diagramBlock.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "graph TD + A[Start] --> B[End]", + "type": "text", + }, + ], + "id": "1", + "props": {}, + "type": "diagram", + }, +] \ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/parse/parseTestInstances.ts b/tests/src/unit/react/formatConversion/parse/parseTestInstances.ts new file mode 100644 index 0000000000..4cfd6479d0 --- /dev/null +++ b/tests/src/unit/react/formatConversion/parse/parseTestInstances.ts @@ -0,0 +1,64 @@ +import { ParseTestCase } from "../../../shared/formatConversion/parse/parseTestCase.js"; +import { + testParseHTML, + testParseMarkdown, +} from "../../../shared/formatConversion/parse/parseTestExecutors.js"; +import { TestInstance } from "../../../types.js"; +import { + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema, +} from "../../testSchema.js"; + +// NOTE: Only the block is covered in these tests. Inline `` is foreign (non-HTML) content, +// and ProseMirror's clipboard parser in the jsdom unit environment doesn't run the inline +// content's `parseContent` for it, so pasting the copied inline math doubles its source here. It +// round-trips correctly in a real browser and should be covered by the browser-mode e2e suite +// instead. +export const parseTestInstancesHTML: TestInstance< + ParseTestCase, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = [ + { + testCase: { + name: "mathBlock", + content: `a2+b2=c2a^2 + b^2 = c^2`, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "diagramBlock", + content: `
graph TD\n  A[Start] --> B[End]
`, + }, + executeTest: testParseHTML, + }, +]; + +// The diagram block's fenced-code representation should also round-trip from +// Markdown. +export const parseTestInstancesMarkdown: TestInstance< + ParseTestCase, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = [ + { + testCase: { + name: "diagramBlock", + content: "```mermaid\ngraph TD\n A[Start] --> B[End]\n```", + }, + executeTest: testParseMarkdown, + }, + // The reverse precedence check: with the diagram block registered, fences + // in other languages must still fall through to the code block. + { + testCase: { + name: "codeBlockNotDiagram", + content: "```javascript\nconsole.log('hi');\n```", + }, + executeTest: testParseMarkdown, + }, +]; diff --git a/tests/src/unit/react/formatConversion/parse/runTests.test.ts b/tests/src/unit/react/formatConversion/parse/runTests.test.ts new file mode 100644 index 0000000000..7f13214bea --- /dev/null +++ b/tests/src/unit/react/formatConversion/parse/runTests.test.ts @@ -0,0 +1,30 @@ +import { describe, it } from "vite-plus/test"; + +import { setupTestEditor } from "../../setupTestEditor.js"; +import { testSchema } from "../../testSchema.js"; +import { + parseTestInstancesHTML, + parseTestInstancesMarkdown, +} from "./parseTestInstances.js"; + +// Tests for verifying that the React math block and inline math implementations +// are correctly parsed from external HTML (MathML). +describe("React parse tests (HTML)", () => { + const getEditor = setupTestEditor(testSchema); + + for (const { testCase, executeTest } of parseTestInstancesHTML) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); + +describe("React parse tests (Markdown)", () => { + const getEditor = setupTestEditor(testSchema); + + for (const { testCase, executeTest } of parseTestInstancesMarkdown) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); diff --git a/tests/src/unit/react/testSchema.tsx b/tests/src/unit/react/testSchema.tsx index aa6ebff0f8..a095917a13 100644 --- a/tests/src/unit/react/testSchema.tsx +++ b/tests/src/unit/react/testSchema.tsx @@ -3,6 +3,11 @@ import { createPageBreakBlockSpec, defaultProps, } from "@blocknote/core"; +import { createReactDiagramBlockSpec } from "@blocknote/diagram-block"; +import { + createReactInlineMathSpec, + createReactMathBlockSpec, +} from "@blocknote/math-block"; import { createReactBlockSpec, createReactInlineContentSpec, @@ -168,10 +173,13 @@ export const testSchema = BlockNoteSchema.create().extend({ customParagraph: createCustomParagraph(), simpleCustomParagraph: createSimpleCustomParagraph(), contextParagraph: createContextParagraph(), + mathBlock: createReactMathBlockSpec(), + diagram: createReactDiagramBlockSpec(), }, inlineContentSpecs: { mention: Mention, tag: Tag, + math: createReactInlineMathSpec(), }, styleSpecs: { small: Small, diff --git a/tests/src/utils/context.ts b/tests/src/utils/context.ts index 189f2e7738..ad25d7157f 100644 --- a/tests/src/utils/context.ts +++ b/tests/src/utils/context.ts @@ -1,47 +1,17 @@ -import { createUserEvent, page } from "vite-plus/test/browser/context"; +import { commands, page, server, userEvent } from "vite-plus/test/browser"; -// This vite-plus build's `@vitest/browser/context` runtime only exports -// `createUserEvent`, `page`, `cdp`, `locators`, `utils` — there is no default -// `userEvent`, `server`, or `commands` export (the published `.d.ts` lists them, -// but they aren't in the runtime bundle). So we adapt here and re-export a -// single shared API surface for the e2e utils + tests to consume. - -/** Shared userEvent instance (preserves keyboard/pointer state across calls). */ -export const userEvent = createUserEvent(); - -export { page }; - -/** - * Triggers a custom browser command registered in `vite.config.browser.ts` - * (e.g. `positionalMouse`). The public `server.commands` API isn't exported in - * this build, so we go through the browser runner directly. - */ -export function triggerCommand( - name: string, - args: unknown[] = [], -): Promise { - return (window as any).__vitest_browser_runner__.commands.triggerCommand( - name, - args, - ); -} - -const ua = navigator.userAgent; +// `vite-plus/test/browser` re-exports `vitest/browser`, which Vitest replaces +// with a generated virtual module while running in Browser Mode. Import it from +// this single place so the e2e utils + tests share one `userEvent` instance +// (it preserves keyboard/pointer state across calls) and one command surface. +export { commands, page, server, userEvent }; /** The browser instance the current test is running in. */ -export const browserName: "chromium" | "firefox" | "webkit" = /Firefox/.test(ua) - ? "firefox" - : /Chrome|Chromium|HeadlessChrome/.test(ua) - ? "chromium" - : "webkit"; +export const browserName = server.browser as "chromium" | "firefox" | "webkit"; /** * Platform modifier for `userEvent.keyboard` (Cmd on macOS, Ctrl elsewhere) — - * the equivalent of Playwright's `ControlOrMeta`. Derived from the browser the - * test runs in, since `server.platform` isn't available. + * the equivalent of Playwright's `ControlOrMeta`. */ -export const MOD: "Meta" | "Control" = /Mac|iPhone|iPad/i.test( - navigator.platform || ua, -) - ? "Meta" - : "Control"; +export const MOD: "Meta" | "Control" = + server.platform === "darwin" ? "Meta" : "Control"; diff --git a/tests/src/utils/editor.ts b/tests/src/utils/editor.ts index 708e4326a0..278e1119cf 100644 --- a/tests/src/utils/editor.ts +++ b/tests/src/utils/editor.ts @@ -1,5 +1,5 @@ import { expect, vi } from "vite-plus/test"; -import type { Locator } from "vite-plus/test/browser/context"; +import type { Locator } from "vite-plus/test/browser"; import { userEvent } from "./context.js"; import { EDITOR_SELECTOR } from "./const.js"; diff --git a/tests/src/utils/mouse.ts b/tests/src/utils/mouse.ts index 184dd350cf..792d15fe0a 100644 --- a/tests/src/utils/mouse.ts +++ b/tests/src/utils/mouse.ts @@ -1,12 +1,24 @@ -import { triggerCommand } from "./context.js"; +import { commands } from "./context.js"; import { DRAG_HANDLE_SELECTOR } from "./const.js"; import { sleep, waitForSelector } from "./editor.js"; -import type { MouseAction } from "./positionalMouse.js"; +import type { MouseAction, PositionalMouseCommand } from "./positionalMouse.js"; // `positionalMouse` is registered as a browser command in vite.config.browser.ts. // `import type` above keeps the (Node-only) command module out of the browser bundle. +// +// Custom commands are normally declared by augmenting Vitest's `BrowserCommands` +// interface, but that isn't possible here: it's declared in `vitest/internal/browser`, +// which the context modules only import (never re-export), and `vitest` isn't a +// direct dependency of this project — we go through `vite-plus/test/browser`. +// Augmenting a module that merely re-exports declares an unrelated interface, so +// the command is typed at this single boundary instead, against the signature +// derived from its implementation. +const browserCommands = commands as typeof commands & { + positionalMouse: PositionalMouseCommand; +}; + function runMouse(actions: MouseAction[]): Promise { - return triggerCommand("positionalMouse", actions); + return browserCommands.positionalMouse(...actions); } /** Bounding rect of an element, resolved from a selector or the element itself. */ diff --git a/tests/src/utils/positionalMouse.ts b/tests/src/utils/positionalMouse.ts index 92a865e6d8..adc3e63ce4 100644 --- a/tests/src/utils/positionalMouse.ts +++ b/tests/src/utils/positionalMouse.ts @@ -4,7 +4,6 @@ import type { FrameLocator, Page, } from "@playwright/test"; -import type {} from "vite-plus/test/browser"; import type { BrowserCommand } from "vite-plus/test/node"; // Vite+ overrides `BrowserCommandContext` with itself, but for some reason it uses: @@ -26,6 +25,17 @@ export type MouseAction = | { type: "up" } | { type: "click"; x: number; y: number; clickCount?: number }; +/** + * Browser-side signature of the {@link positionalMouse} command below, i.e. what + * `BrowserCommand` turns into once Vitest strips the (Node-only) + * `BrowserCommandContext` first parameter. Used by `mouse.ts` to type the + * command on the browser `commands` object — see the note there on why this + * can't be a `declare module` augmentation. + */ +export type PositionalMouseCommand = ( + ...actions: MouseAction[] +) => Promise; + /** * Vitest's `userEvent` doesn't have several mouse commands that we relied on in Playwright, namely * clicking at the current mouse position, and moving the mouse to a given position across N steps. @@ -69,10 +79,3 @@ export const positionalMouse: BrowserCommand = async ( } } }; - -// Add command to types, as registering it in `vite.config.browser.ts` isn't enough. -declare module "vite-plus/test/browser" { - interface BrowserCommands { - positionalMouse: (...actions: MouseAction[]) => Promise; - } -} diff --git a/tests/src/utils/screenshotFull.ts b/tests/src/utils/screenshotFull.ts new file mode 100644 index 0000000000..0078de75c3 --- /dev/null +++ b/tests/src/utils/screenshotFull.ts @@ -0,0 +1,42 @@ +import { page } from "./context.js"; +import { expectElement } from "./editor.js"; + +/** + * Screenshots an element that may be taller than the browser window, at full + * resolution. + * + * Plain element screenshots only contain what the tester iframe actually + * paints: anything below its ~720px fold comes out blank white, silently. + * Growing the iframe first (`page.viewport`) makes it paint everything, but + * the harness then scales the iframe down to fit the window via a CSS + * transform on its wrapper, and the capture shrinks with it (static.test.tsx + * documents accepting that trade-off). So this grows the iframe past the + * content and neutralizes the wrapper's scale transform (same origin) for + * the duration of the capture - Playwright captures beyond the window fine. + * + * This reaches into harness DOM internals, but it is exactly what upstream + * Vitest does since the fix for vitest-dev/vitest#9124 / #9363: during + * captures, PR vitest-dev/vitest#9745 (milestone 5.0.0) un-scales the + * iframe and resizes the headless viewport to the screenshot. Delete this + * util once vite-plus ships Vitest 5's browser mode. If an upgrade changes + * the wrapper DOM before then, captures come out downscaled and fail the + * baselines' dimension checks - loudly, not silently; + * screenshotFull.test.tsx isolates that breakage on synthetic content. + */ +export async function screenshotFull(element: HTMLElement, name: string) { + const height = Math.max( + 720, + Math.ceil(element.getBoundingClientRect().bottom) + 40, + ); + await page.viewport(1280, height); + (window.frameElement?.parentElement as HTMLElement | null)?.style.setProperty( + "transform", + "none", + ); + try { + await expectElement(element).toMatchScreenshot(name); + } finally { + // Re-lays-out the wrapper, including its transform. + await page.viewport(1280, 720); + } +} diff --git a/tests/src/vitest-browser.d.ts b/tests/src/vitest-browser.d.ts deleted file mode 100644 index abb2734028..0000000000 --- a/tests/src/vitest-browser.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This vite-plus build's `@vitest/browser/context` runtime exports -// `createUserEvent` (a factory), but its shipped `.d.ts` only declares the -// default `userEvent`. Augment the module type to match the runtime so -// `src/utils/context.ts` type-checks. -import type { UserEvent } from "vite-plus/test/browser/context"; - -declare module "vite-plus/test/browser/context" { - export function createUserEvent(): UserEvent; -} diff --git a/tests/vite.config.browser.ts b/tests/vite.config.browser.ts index a4f19f3fcf..21fb2a1e1b 100644 --- a/tests/vite.config.browser.ts +++ b/tests/vite.config.browser.ts @@ -1,7 +1,7 @@ +import tailwindcss from "@tailwindcss/vite"; import * as fs from "fs"; import * as path from "path"; -import tailwindcss from "@tailwindcss/vite"; -import { configDefaults, defineConfig, type UserConfig } from "vite-plus"; +import { defineConfig, type UserConfig } from "vite-plus"; import { playwright } from "vite-plus/test/browser/providers/playwright"; import { positionalMouse } from "./src/utils/positionalMouse.js"; @@ -67,16 +67,27 @@ export default defineConfig( // src/examples.d.ts. alias: { ...blockNoteSrcAliases, - // `@blocknote/shared` lives at the repo root (not under packages/), so - // it isn't picked up by the packages scan above. The suggestion-gallery - // scenarios import the shared `testDocument` from it. - "@blocknote/shared": path.resolve(__dirname, "../shared"), + // The shared test-utils package lives at the repo root (not under + // packages/), so it isn't picked up by the packages scan above. + // All consumers - test code and example apps alike - import it via + // the repo-wide `@shared` path alias (matching the packages' vite + // and tsconfig setups); the package itself is private, so its name + // resolves nowhere outside the workspace anyway. + "@shared": path.resolve(__dirname, "../shared"), "@examples": path.resolve(__dirname, "../examples"), }, }, test: { name: "e2e", - include: ["./src/end-to-end/**/*.test.tsx"], + // Besides the end-to-end tests, this suite also runs the packages' + // `.browser.test` files: unit tests for browser-only implementations + // (canvas rasterization, Mermaid rendering), colocated with the code + // they test but needing a real browser. The packages' own (node) + // vitest configs exclude them. + include: [ + "./src/end-to-end/**/*.test.tsx", + "../packages/*/src/**/*.browser.test.{ts,tsx}", + ], setupFiles: ["./vitestSetup.browser.ts"], // Running three browsers concurrently inside one Docker container already // saturates CPU; layering per-browser file parallelism on top causes diff --git a/tests/vite.config.ts b/tests/vite.config.ts index 650c20d409..bdbf19262c 100644 --- a/tests/vite.config.ts +++ b/tests/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig( run: { tasks: { build: { - command: "tsgo", + command: "tsc", input: [ { auto: true }, { pattern: "!**/*.tsbuildinfo", base: "workspace" }, @@ -53,6 +53,14 @@ export default defineConfig( __dirname, "../packages/mantine/src/", ), + "@blocknote/math-block": path.resolve( + __dirname, + "../packages/math-block/src/", + ), + "@blocknote/diagram-block": path.resolve( + __dirname, + "../packages/diagram-block/src/", + ), "@blocknote/server-util": path.resolve( __dirname, "../packages/server-util/src/", diff --git a/tests/vitestSetup.browser.ts b/tests/vitestSetup.browser.ts index 9333a919a1..469a859137 100644 --- a/tests/vitestSetup.browser.ts +++ b/tests/vitestSetup.browser.ts @@ -1,5 +1,5 @@ import { afterEach, beforeAll, beforeEach } from "vite-plus/test"; -import { page } from "vite-plus/test/browser/context"; +import { page } from "vite-plus/test/browser"; // Browser-mode setup. Unlike the jsdom `vitestSetup.ts`, we don't mock // ClipboardEvent/DragEvent/matchMedia here — the real browser provides them.