diff --git a/.claude/skills/create-example/SKILL.md b/.claude/skills/create-example/SKILL.md new file mode 100644 index 0000000000..dbf95af451 --- /dev/null +++ b/.claude/skills/create-example/SKILL.md @@ -0,0 +1,72 @@ +--- +name: create-example +description: Creates a new example under the `/examples` directory. +--- + +This skill provides instructions on how to create a BlockNote editor example correctly. + +# Creating an example root directory + +Under the `/examples` directory, each subdirectory is a category of examples. It's name consists of a 2 digit index, followed by a dash and the category name. + +Each of these contains another set of subdirectories, where each one contains a single example. The naming of these is the same, but the category name is swapped for the example name. + +Based on the user's prompt, the most relevant category should be chosen, and a new directory for the example should be created. The index in the example directory's name should be the lowest unused one to avoid large diffs from having to reorder & rename the existing example directories. It is very unlikely that a new category directory should need to be created for the new example, but it should use the same convention. + +# Source & metadata files + +## Source files + +Any source files must be inside a `/src` directory at the root of the example directory. Within these, there must also be an `App.tsx` file, which default exports a React component. This component is responsible for rendering the entire example. + +## Metadata files + +There are two files containing metadata that must also be added at the root of the example directory: + +`.bnexample.json` + +Contains all of the example's configuration. Here's an annotated example (from `/examples/03-ui-components/13-custom-ui/.bnexample.json`): + +``` + +"playground": true, + +"docs": true, + +"author": "matthewlipski", + +"tags": [ + "Advanced", + "Inline Content", + "UI Components", + "Block Side Menu", + "Formatting Toolbar", + "Suggestion Menus", + "Slash Menu", + "Appearance & Styling" +], + +"dependencies": { + "@mui/icons-material": "^5.16.1", + "@mui/material": "^5.16.1" +}, + +"pro": true +``` + +`README.md` + +A Markdown description of the example. Made of four parts: + +1. Heading with the example name. This does not necessarily need to be the same as the example directory name and can be more verbose. +2. Description of the example, which should be no longer than a paragraph of three sentences. +3. An optional "Try me out!" callout. Should be a single sentence instructing the user how to see the changes made to the editor in the example. +4. A list of relevant docs. These are mostly internal but may also refer to e.g. dependencies used in the example. + +See `/examples/07-collaboration/01-partykit` for reference on the exact markup of these sections. + +# Generated files + +Once the source & metadata files are done, `vp run gen` should be executed from the project root directory to auto-generate additional files in the example directory, as well as the playground & docs. + +One of the generated files is `package.json` in the example directory. If the `bnexample.json` specified any dependencies, these will be included here. Therefore, `vp install` should always be executed in the project root directory after, to ensure these are installed. diff --git a/.claude/skills/debug-skill/SKILL.md b/.claude/skills/debug-skill/SKILL.md new file mode 100644 index 0000000000..cc47080e23 --- /dev/null +++ b/.claude/skills/debug-skill/SKILL.md @@ -0,0 +1,70 @@ +--- +name: debug-skill +description: Instructions for navigating and debugging BlockNote in a browser. Shows how to open specific menus & toolbars, as well select content. Should be used when prompted to fix a bug that requires inspecting the editor's appearance or rendered HTML. +--- + +# General loop + +When fixing a bug, the following feedback loop should be used. + +1. Apply a code change that you think will fix the issue. +2. Test the change in a browser environment. +3. Take screenshots to verify that the issue is fixed. +4. Repeat until the bug is indeed fixed. + +# Browser environment + +Before starting up a browser environment, you need to ensure the dev server is running. This can be done by checking if port 5173 is in use. If it isn't, running `vp run dev` at the project root will start the server. + +The Playwright CLI should be used for the browser environment. It can be used to navigate to the dev server and programmatically issue mouse clicks/keyboard inputs. If not installed, stop what you're doing and notify the user to install it. + +# Selecting an example + +After navigating to `localhost:5173`, an example must be selected. These are listed in the navbar (`mantine-AppShell-navbar` CSS class). The "Default Schema Showcase" should be selected, unless stated otherwise by the user. + +Each example will contain a BlockNote editor, and possibly additional elements like text fields or static toolbars. + +# Editor HTML structure + +Below is a list of elements that make up a BlockNote editor. This is helpful for mapping BlockNote concepts to what's actually visible in the browser. The nesting of the list items is representative of how the corresponding elements are nested in the rendered HTML. The elements are referenced by their main CSS class. + +- `bn-container`: Wrapper element for the editor. + - `bn-editor`: Root element for the BlockNote editor. + - `bn-block-group`: Root container for blocks. + - `bn-block-outer`: Wrapper element for a block. + - `bn-block`: Root element for a block. + - `bn-block-content`: Container element for all content rendered by the block itself. Also renders a `data-content-type` attribute which stores the block's type, and additional `data-*` for every non-default prop that the block has. + - `bn-inline-content`: Container element for user-editable rich text within a block. Note that not all blocks will contain this element. + - `bn-block-group`: Container for nested blocks. Note that if a block doesn't contain nested blocks, it won't have this element. + - `bn-block-column-list`: Container element for columns. + - `bn-block-column`: Column element containing blocks. + +Each element only appears once in its parent, except `bn-block-outer` and `bn-block-column-list`, which can appear multiple times. + +Each `bn-block-group` and `bn-block-column` also contain `bn-block-outer` elements. These are not listed as they can be nested to an arbitrary depth. + +Note that additional UI elements like menus and toolbars are mounted in a portal attached to the `body`. + +# Keyboard navigation + +Assume you are on a machine running macOS. You can use the following key combinations to navigate through the editor and create selections: + +- Left/Right Arrow: Moves the text cursor back/forward one character. +- Up/Down Arrow: Moves the text cursor to the previous/next block. +- Option + Left/Right Arrow: Moves the text cursor to the start/end of the current word. If already at the start/end of a word, moves it to the start/end of the previous/next one instead. +- Cmd + Left/Right Arrow: Moves the text cursor to the start/end of the line. +- Cmd + Up/Down Arrow: Moves the text cursor to the start/end of the document. + +Each of these can also be used with Shift to create/extend a selection instead of just moving the cursor. + +It is extremely important to note that these key combinations are only relevant for debugging and NOT for writing end-to-end tests. While Playwright is used for both, tests run in a Linux environment which has different bindings for keyboard navigation. + +# Opening menus & toolbars + +Here are the most often used UI elements, and how to find/open them. + +- **Formatting toolbar**: Create a selection using the keyboard and look for an element with the `bn-formatting-toolbar` CSS class. Buttons/dropdowns within it should be interacted with using the mouse instead. The `data-test` attribute will inform you what a given button or dropdown is for. Press escape to dismiss the toolbar. +- **Side menu**: Hover a block with the mouse, i.e. a `bn-block` element, and look for an element with the `bn-side-menu` CSS class. Unless specified otherwise, it contains a button to add a block ("Add block" ARIA label) and a drag handle which opens a menu on click ("Open block menu" ARIA label). Typing in the editor or moving the mouse cursor above/below it will hide the side menu, unless the drag handle menu is open. Then, it's "frozen" until dismissed by an outside click or pressing Escape. +- **Slash menu**: Type the "/" key while in a block and look for an element with the `bn-suggestion-menu` CSS class. It contains a list of items with the `bn-suggestion-menu-item` CSS class. While the menu is open, the up/down arrows navigate through items instead of moving the text cursor. Items can be triggered with a mouse click or pressing Enter while selected. Each item will convert the type of the current block to one of a given type, if it's empty. Otherwise, it will create a new block below with that type. The `bn-suggestion-menu-item-title` element's text content will indicate the new type. Pressing Escape closes the menu. +- **Link toolbar**: Hover a link in a block (anchor element within a `bn-inline-content` element), or move the text selection inside it using the arrow keys, and look for an element with the `bn-link-toolbar` CSS class. Unless specified otherwise, it contains three buttons. The first has the text, "Edit link". On click, it opens a popup to edit the link text and URL. You can locate these inputs with the "Edit title" and "Edit URL" placeholders. The other two buttons are for opening the link in a new tab ("Open in new tab" ARIA label) and deleting the link ("Remove link" ARIA label). If the toolbar was opened via mouse hover, moving the mouse off of the link or toolbar will close it after half a second. Otherwise, moving the text cursor outside the link will close the toolbar. It can also be dismissed by pressing Escape. +- **File panel**: After creating a file, image, video, or audio block, it will render a button with the text "Add file" (`bn-add-file-button` CSS class). Clicking the button will open the file panel. When the block is created using the slash menu (typically the case), the file panel will be open immediately. It always has an "Embed" tab (`data-test="embed-tab"` attribute). While this tab is selected, the file panel displays an input for the file URL ("Enter URL" placeholder) and "Embed file" button. For some examples, an "Upload" tab (`data-test="upload-tab"` attribute) will also be present. While it's selected, the file panel will display a file input (`data-test="upload-input"` attribute). After embedding/uploading a file, the block will render said file instead of displaying the "Add file" button. diff --git a/.claude/skills/playwright-cli/SKILL.md b/.claude/skills/playwright-cli/SKILL.md new file mode 100644 index 0000000000..fe30992ad9 --- /dev/null +++ b/.claude/skills/playwright-cli/SKILL.md @@ -0,0 +1,394 @@ +--- +name: playwright-cli +description: Automate browser interactions, test web pages and work with Playwright tests. +allowed-tools: Bash(playwright-cli:*) Bash(npx:*) Bash(npm:*) +--- + +# Browser Automation with playwright-cli + +## Quick start + +```bash +# open new browser +playwright-cli open +# navigate to a page +playwright-cli goto https://playwright.dev +# interact with the page using refs from the snapshot +playwright-cli click e15 +playwright-cli type "page.click" +playwright-cli press Enter +# take a screenshot (rarely used, as snapshot is more common) +playwright-cli screenshot +# close the browser +playwright-cli close +``` + +## Commands + +### Core + +```bash +playwright-cli open +# open and navigate right away +playwright-cli open https://example.com/ +playwright-cli goto https://playwright.dev +playwright-cli type "search query" +playwright-cli click e3 +playwright-cli dblclick e7 +# --submit presses Enter after filling the element +playwright-cli fill e5 "user@example.com" --submit +playwright-cli drag e2 e8 +# drop files or data onto an element (from outside the page) +playwright-cli drop e4 --path=./image.png +playwright-cli drop e4 --data="text/plain=hello world" +playwright-cli hover e4 +playwright-cli select e9 "option-value" +playwright-cli upload ./document.pdf +playwright-cli check e12 +playwright-cli uncheck e12 +playwright-cli snapshot +playwright-cli eval "document.title" +playwright-cli eval "el => el.textContent" e5 +# get element id, class, or any attribute not visible in the snapshot +playwright-cli eval "el => el.id" e5 +playwright-cli eval "el => el.getAttribute('data-testid')" e5 +playwright-cli dialog-accept +playwright-cli dialog-accept "confirmation text" +playwright-cli dialog-dismiss +playwright-cli resize 1920 1080 +playwright-cli close +``` + +### Navigation + +```bash +playwright-cli go-back +playwright-cli go-forward +playwright-cli reload +``` + +### Keyboard + +```bash +playwright-cli press Enter +playwright-cli press ArrowDown +playwright-cli keydown Shift +playwright-cli keyup Shift +``` + +### Mouse + +```bash +playwright-cli mousemove 150 300 +playwright-cli mousedown +playwright-cli mousedown right +playwright-cli mouseup +playwright-cli mouseup right +playwright-cli mousewheel 0 100 +``` + +### Save as + +Also resize large screenshots with sips (native macOS tool) if possible. + +Screenshots should be saved to `.claude/skills/playwright-cli/screenshots/` in the workspace root. + +```bash +playwright-cli screenshot +playwright-cli screenshot e5 +playwright-cli screenshot --filename=page.png +playwright-cli pdf --filename=page.pdf +``` + +### Tabs + +```bash +playwright-cli tab-list +playwright-cli tab-new +playwright-cli tab-new https://example.com/page +playwright-cli tab-close +playwright-cli tab-close 2 +playwright-cli tab-select 0 +``` + +### Storage + +```bash +playwright-cli state-save +playwright-cli state-save auth.json +playwright-cli state-load auth.json + +# Cookies +playwright-cli cookie-list +playwright-cli cookie-list --domain=example.com +playwright-cli cookie-get session_id +playwright-cli cookie-set session_id abc123 +playwright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure +playwright-cli cookie-delete session_id +playwright-cli cookie-clear + +# LocalStorage +playwright-cli localstorage-list +playwright-cli localstorage-get theme +playwright-cli localstorage-set theme dark +playwright-cli localstorage-delete theme +playwright-cli localstorage-clear + +# SessionStorage +playwright-cli sessionstorage-list +playwright-cli sessionstorage-get step +playwright-cli sessionstorage-set step 3 +playwright-cli sessionstorage-delete step +playwright-cli sessionstorage-clear +``` + +### Network + +```bash +playwright-cli route "**/*.jpg" --status=404 +playwright-cli route "https://api.example.com/**" --body='{"mock": true}' +playwright-cli route-list +playwright-cli unroute "**/*.jpg" +playwright-cli unroute +``` + +### DevTools + +```bash +playwright-cli console +playwright-cli console warning +playwright-cli requests +playwright-cli request 5 +playwright-cli run-code "async page => await page.context().grantPermissions(['geolocation'])" +playwright-cli run-code --filename=script.js +playwright-cli tracing-start +playwright-cli tracing-stop +playwright-cli video-start video.webm +playwright-cli video-chapter "Chapter Title" --description="Details" --duration=2000 +playwright-cli video-stop + +# launch the dashboard for UI review / design feedback — user annotates the page, you receive the annotated screenshot, snapshot, and notes +playwright-cli show --annotate + +# generate a Playwright locator for an element from its ref or selector +playwright-cli generate-locator e5 --raw + +# show a persistent highlight overlay for an element, optionally with a custom style +playwright-cli highlight e5 +playwright-cli highlight e5 --style="outline: 3px dashed red" +# hide a single element highlight, or all page highlights when no target is given +playwright-cli highlight e5 --hide +playwright-cli highlight --hide +``` + +## Raw output + +The global `--raw` option strips page status, generated code, and snapshot sections from the output, returning only the result value. Use it to pipe command output into other tools. Commands that don't produce output return nothing. + +```bash +playwright-cli --raw eval "JSON.stringify(performance.timing)" | jq '.loadEventEnd - .navigationStart' +playwright-cli --raw eval "JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))" > links.json +playwright-cli --raw snapshot > before.yml +playwright-cli click e5 +playwright-cli --raw snapshot > after.yml +diff before.yml after.yml +TOKEN=$(playwright-cli --raw cookie-get session_id) +playwright-cli --raw localstorage-get theme +``` + +For structured output wrapping every reply as JSON, pass --json + +```bash +playwright-cli list --json +``` + +## Open parameters + +```bash +# Use specific browser when creating session +playwright-cli open --browser=chrome +playwright-cli open --browser=firefox +playwright-cli open --browser=webkit +playwright-cli open --browser=msedge + +# Use persistent profile (by default profile is in-memory) +playwright-cli open --persistent +# Use persistent profile with custom directory +playwright-cli open --profile=/path/to/profile + +# Connect to browser via Playwright Extension +playwright-cli attach --extension=chrome + +# Connect to a running Chrome or Edge by channel name +playwright-cli attach --cdp=chrome +playwright-cli attach --cdp=msedge + +# Connect to a running browser via CDP endpoint +playwright-cli attach --cdp=http://localhost:9222 + +# Start with config file +playwright-cli open --config=my-config.json + +# Close the browser +playwright-cli close +# Detach from an attached browser (leaves the external browser running) +playwright-cli -s=msedge detach +# Delete user data for the default session +playwright-cli delete-data +``` + +## Snapshots + +After each command, playwright-cli provides a snapshot of the current browser state. + +```bash +> playwright-cli goto https://example.com +### Page +- Page URL: https://example.com/ +- Page Title: Example Domain +### Snapshot +[Snapshot](.playwright-cli/page-2026-02-14T19-22-42-679Z.yml) +``` + +You can also take a snapshot on demand using `playwright-cli snapshot` command. All the options below can be combined as needed. + +```bash +# default - save to a file with timestamp-based name +playwright-cli snapshot + +# save to file, use when snapshot is a part of the workflow result +playwright-cli snapshot --filename=after-click.yaml + +# snapshot an element instead of the whole page +playwright-cli snapshot "#main" + +# limit snapshot depth for efficiency, take a partial snapshot afterwards +playwright-cli snapshot --depth=4 +playwright-cli snapshot e34 + +# include each element's bounding box as [box=x,y,width,height] +playwright-cli snapshot --boxes +``` + +## Targeting elements + +By default, use refs from the snapshot to interact with page elements. + +```bash +# get snapshot with refs +playwright-cli snapshot + +# interact using a ref +playwright-cli click e15 +``` + +You can also use css selectors or Playwright locators. + +```bash +# css selector +playwright-cli click "#main > button.submit" + +# role locator +playwright-cli click "getByRole('button', { name: 'Submit' })" + +# test id +playwright-cli click "getByTestId('submit-button')" +``` + +## Browser Sessions + +```bash +# create new browser session named "mysession" with persistent profile +playwright-cli -s=mysession open example.com --persistent +# same with manually specified profile directory (use when requested explicitly) +playwright-cli -s=mysession open example.com --profile=/path/to/profile +playwright-cli -s=mysession click e6 +playwright-cli -s=mysession close # stop a named browser +playwright-cli -s=mysession delete-data # delete user data for persistent session + +playwright-cli list +# Close all browsers +playwright-cli close-all +# Forcefully kill all browser processes +playwright-cli kill-all +``` + +## Installation + +If global `playwright-cli` command is not available, try a local version via `npx playwright-cli`: + +```bash +npx --no-install playwright-cli --version +``` + +When local version is available, use `npx playwright-cli` in all commands. Otherwise, install `playwright-cli` as a global command: + +```bash +npm install -g @playwright/cli@latest +``` + +## Example: Form submission + +```bash +playwright-cli open https://example.com/form +playwright-cli snapshot + +playwright-cli fill e1 "user@example.com" +playwright-cli fill e2 "password123" +playwright-cli click e3 +playwright-cli snapshot +playwright-cli close +``` + +## Example: Multi-tab workflow + +```bash +playwright-cli open https://example.com +playwright-cli tab-new https://example.com/other +playwright-cli tab-list +playwright-cli tab-select 0 +playwright-cli snapshot +playwright-cli close +``` + +## Example: Debugging with DevTools + +```bash +playwright-cli open https://example.com +playwright-cli click e4 +playwright-cli fill e7 "test" +playwright-cli console +playwright-cli requests +playwright-cli close +``` + +```bash +playwright-cli open https://example.com +playwright-cli tracing-start +playwright-cli click e4 +playwright-cli fill e7 "test" +playwright-cli tracing-stop +playwright-cli close +``` + +## Example: Interactive session + +Ask the user for UI review or design feedback. The user draws boxes on the live page and types comments; you receive the annotated screenshot, the snapshot of the marked region, and the user's notes. Use this whenever the user asks for "UI review", "design feedback", or to "ask the user what they think / want / mean": + +```bash +playwright-cli open https://example.com +playwright-cli show --annotate +``` + +## Specific tasks + +- **Running and Debugging Playwright tests** [references/playwright-tests.md](references/playwright-tests.md) +- **Request mocking** [references/request-mocking.md](references/request-mocking.md) +- **Running Playwright code** [references/running-code.md](references/running-code.md) +- **Browser session management** [references/session-management.md](references/session-management.md) +- **Spec-driven testing (plan / generate / heal)** [references/spec-driven-testing.md](references/spec-driven-testing.md) +- **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md) +- **Test generation** [references/test-generation.md](references/test-generation.md) +- **Tracing** [references/tracing.md](references/tracing.md) +- **Video recording** [references/video-recording.md](references/video-recording.md) +- **Inspecting element attributes** [references/element-attributes.md](references/element-attributes.md) diff --git a/.claude/skills/playwright-cli/references/element-attributes.md b/.claude/skills/playwright-cli/references/element-attributes.md new file mode 100644 index 0000000000..4e9fa6b991 --- /dev/null +++ b/.claude/skills/playwright-cli/references/element-attributes.md @@ -0,0 +1,23 @@ +# Inspecting Element Attributes + +When the snapshot doesn't show an element's `id`, `class`, `data-*` attributes, or other DOM properties, use `eval` to inspect them. + +## Examples + +```bash +playwright-cli snapshot +# snapshot shows a button as e7 but doesn't reveal its id or data attributes + +# get the element's id +playwright-cli eval "el => el.id" e7 + +# get all CSS classes +playwright-cli eval "el => el.className" e7 + +# get a specific attribute +playwright-cli eval "el => el.getAttribute('data-testid')" e7 +playwright-cli eval "el => el.getAttribute('aria-label')" e7 + +# get a computed style property +playwright-cli eval "el => getComputedStyle(el).display" e7 +``` diff --git a/.claude/skills/playwright-cli/references/playwright-tests.md b/.claude/skills/playwright-cli/references/playwright-tests.md new file mode 100644 index 0000000000..bec2ec90e4 --- /dev/null +++ b/.claude/skills/playwright-cli/references/playwright-tests.md @@ -0,0 +1,39 @@ +# Running Playwright Tests + +To run Playwright tests, use the `npx playwright test` command, or a package manager script. To avoid opening the interactive html report, use `PLAYWRIGHT_HTML_OPEN=never` environment variable. + +```bash +# Run all tests +PLAYWRIGHT_HTML_OPEN=never npx playwright test + +# Run all tests through a custom npm script +PLAYWRIGHT_HTML_OPEN=never npm run special-test-command +``` + +# Debugging Playwright Tests + +To debug a failing Playwright test, run it with `--debug=cli` option. This command will pause the test at the start and print the debugging instructions. + +**IMPORTANT**: run the command in the background and check the output until "Debugging Instructions" is printed. Make sure to stop the command after you have finished. + +Once instructions containing a session name are printed, use `playwright-cli` to attach the session and explore the page. + +```bash +# Run the test +PLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli +# ... +# ... debugging instructions for "tw-abcdef" session ... +# ... + +# Attach to the test +playwright-cli attach tw-abcdef +``` + +Keep the test running in the background while you explore and look for a fix. +The test is paused at the start, so you should step over or pause at a particular location +where the problem is most likely to be. + +Every action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. +This code appears in the output and can be copied directly into the test. Most of the time, a specific locator or an expectation should be updated, but it could also be a bug in the app. Use your judgement. + +After fixing the test, stop the background test run. Rerun to check that test passes. diff --git a/.claude/skills/playwright-cli/references/request-mocking.md b/.claude/skills/playwright-cli/references/request-mocking.md new file mode 100644 index 0000000000..9005fda67d --- /dev/null +++ b/.claude/skills/playwright-cli/references/request-mocking.md @@ -0,0 +1,87 @@ +# Request Mocking + +Intercept, mock, modify, and block network requests. + +## CLI Route Commands + +```bash +# Mock with custom status +playwright-cli route "**/*.jpg" --status=404 + +# Mock with JSON body +playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"}]' --content-type=application/json + +# Mock with custom headers +playwright-cli route "**/api/data" --body='{"ok":true}' --header="X-Custom: value" + +# Remove headers from requests +playwright-cli route "**/*" --remove-header=cookie,authorization + +# List active routes +playwright-cli route-list + +# Remove a route or all routes +playwright-cli unroute "**/*.jpg" +playwright-cli unroute +``` + +## URL Patterns + +``` +**/api/users - Exact path match +**/api/*/details - Wildcard in path +**/*.{png,jpg,jpeg} - Match file extensions +**/search?q=* - Match query parameters +``` + +## Advanced Mocking with run-code + +For conditional responses, request body inspection, response modification, or delays: + +### Conditional Response Based on Request + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/login', route => { + const body = route.request().postDataJSON(); + if (body.username === 'admin') { + route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) }); + } else { + route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) }); + } + }); +}" +``` + +### Modify Real Response + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/user', async route => { + const response = await route.fetch(); + const json = await response.json(); + json.isPremium = true; + await route.fulfill({ response, json }); + }); +}" +``` + +### Simulate Network Failures + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/offline', route => route.abort('internetdisconnected')); +}" +# Options: connectionrefused, timedout, connectionreset, internetdisconnected +``` + +### Delayed Response + +```bash +playwright-cli run-code "async page => { + await page.route('**/api/slow', async route => { + await new Promise(r => setTimeout(r, 3000)); + route.fulfill({ body: JSON.stringify({ data: 'loaded' }) }); + }); +}" +``` diff --git a/.claude/skills/playwright-cli/references/running-code.md b/.claude/skills/playwright-cli/references/running-code.md new file mode 100644 index 0000000000..06645ec124 --- /dev/null +++ b/.claude/skills/playwright-cli/references/running-code.md @@ -0,0 +1,240 @@ +# Running Custom Playwright Code + +Use `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands. + +## Syntax + +```bash +playwright-cli run-code "async page => { + // Your Playwright code here + // Access page.context() for browser context operations +}" +``` + +You can also load the function from a file: + +```bash +playwright-cli run-code --filename=./my-script.js +``` + +The code must be a single function expression, it is wrapped in `(...)` and evaluated. +import/export/require syntax is not supported. + +## Geolocation + +```bash +# Grant geolocation permission and set location +playwright-cli run-code "async page => { + await page.context().grantPermissions(['geolocation']); + await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 }); +}" + +# Set location to London +playwright-cli run-code "async page => { + await page.context().grantPermissions(['geolocation']); + await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 }); +}" + +# Clear geolocation override +playwright-cli run-code "async page => { + await page.context().clearPermissions(); +}" +``` + +## Permissions + +```bash +# Grant multiple permissions +playwright-cli run-code "async page => { + await page.context().grantPermissions([ + 'geolocation', + 'notifications', + 'camera', + 'microphone' + ]); +}" + +# Grant permissions for specific origin +playwright-cli run-code "async page => { + await page.context().grantPermissions(['clipboard-read'], { + origin: 'https://example.com' + }); +}" +``` + +## Media Emulation + +```bash +# Emulate dark color scheme +playwright-cli run-code "async page => { + await page.emulateMedia({ colorScheme: 'dark' }); +}" + +# Emulate light color scheme +playwright-cli run-code "async page => { + await page.emulateMedia({ colorScheme: 'light' }); +}" + +# Emulate reduced motion +playwright-cli run-code "async page => { + await page.emulateMedia({ reducedMotion: 'reduce' }); +}" + +# Emulate print media +playwright-cli run-code "async page => { + await page.emulateMedia({ media: 'print' }); +}" +``` + +## Wait Strategies + +```bash +# Wait for network idle +playwright-cli run-code "async page => { + await page.waitForLoadState('networkidle'); +}" + +# Wait for specific element +playwright-cli run-code "async page => { + await page.locator('.loading').waitFor({ state: 'hidden' }); +}" + +# Wait for function to return true +playwright-cli run-code "async page => { + await page.waitForFunction(() => window.appReady === true); +}" + +# Wait with timeout +playwright-cli run-code "async page => { + await page.locator('.result').waitFor({ timeout: 10000 }); +}" +``` + +## Frames and Iframes + +```bash +# Work with iframe +playwright-cli run-code "async page => { + const frame = page.locator('iframe#my-iframe').contentFrame(); + await frame.locator('button').click(); +}" + +# Get all frames +playwright-cli run-code "async page => { + const frames = page.frames(); + return frames.map(f => f.url()); +}" +``` + +## File Downloads + +```bash +# Handle file download +playwright-cli run-code "async page => { + const downloadPromise = page.waitForEvent('download'); + await page.getByRole('link', { name: 'Download' }).click(); + const download = await downloadPromise; + await download.saveAs('./downloaded-file.pdf'); + return download.suggestedFilename(); +}" +``` + +## Clipboard + +```bash +# Read clipboard (requires permission) +playwright-cli run-code "async page => { + await page.context().grantPermissions(['clipboard-read']); + return await page.evaluate(() => navigator.clipboard.readText()); +}" + +# Write to clipboard +playwright-cli run-code "async page => { + await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!'); +}" +``` + +## Page Information + +```bash +# Get page title +playwright-cli run-code "async page => { + return await page.title(); +}" + +# Get current URL +playwright-cli run-code "async page => { + return page.url(); +}" + +# Get page content +playwright-cli run-code "async page => { + return await page.content(); +}" + +# Get viewport size +playwright-cli run-code "async page => { + return page.viewportSize(); +}" +``` + +## JavaScript Execution + +```bash +# Execute JavaScript and return result +playwright-cli run-code "async page => { + return await page.evaluate(() => { + return { + userAgent: navigator.userAgent, + language: navigator.language, + cookiesEnabled: navigator.cookieEnabled + }; + }); +}" + +# Pass arguments to evaluate +playwright-cli run-code "async page => { + const multiplier = 5; + return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier); +}" +``` + +## Error Handling + +```bash +# Try-catch in run-code +playwright-cli run-code "async page => { + try { + await page.getByRole('button', { name: 'Submit' }).click({ timeout: 1000 }); + return 'clicked'; + } catch (e) { + return 'element not found'; + } +}" +``` + +## Complex Workflows + +```bash +# Login and save state +playwright-cli run-code "async page => { + await page.goto('https://example.com/login'); + await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com'); + await page.getByRole('textbox', { name: 'Password' }).fill('secret'); + await page.getByRole('button', { name: 'Sign in' }).click(); + await page.waitForURL('**/dashboard'); + await page.context().storageState({ path: 'auth.json' }); + return 'Login successful'; +}" + +# Scrape data from multiple pages +playwright-cli run-code "async page => { + const results = []; + for (let i = 1; i <= 3; i++) { + await page.goto(\`https://example.com/page/\${i}\`); + const items = await page.locator('.item').allTextContents(); + results.push(...items); + } + return results; +}" +``` diff --git a/.claude/skills/playwright-cli/references/session-management.md b/.claude/skills/playwright-cli/references/session-management.md new file mode 100644 index 0000000000..287e77fb7f --- /dev/null +++ b/.claude/skills/playwright-cli/references/session-management.md @@ -0,0 +1,226 @@ +# Browser Session Management + +Run multiple isolated browser sessions concurrently with state persistence. + +## Named Browser Sessions + +Use `-s` flag to isolate browser contexts: + +```bash +# Browser 1: Authentication flow +playwright-cli -s=auth open https://app.example.com/login + +# Browser 2: Public browsing (separate cookies, storage) +playwright-cli -s=public open https://example.com + +# Commands are isolated by browser session +playwright-cli -s=auth fill e1 "user@example.com" +playwright-cli -s=public snapshot +``` + +## Browser Session Isolation Properties + +Each browser session has independent: + +- Cookies +- LocalStorage / SessionStorage +- IndexedDB +- Cache +- Browsing history +- Open tabs + +## Browser Session Commands + +```bash +# List all browser sessions +playwright-cli list + +# Stop a browser session (close the browser) +playwright-cli close # stop the default browser +playwright-cli -s=mysession close # stop a named browser + +# Stop all browser sessions +playwright-cli close-all + +# Forcefully kill all daemon processes (for stale/zombie processes) +playwright-cli kill-all + +# Delete browser session user data (profile directory) +playwright-cli delete-data # delete default browser data +playwright-cli -s=mysession delete-data # delete named browser data +``` + +## Environment Variable + +Set a default browser session name via environment variable: + +```bash +export PLAYWRIGHT_CLI_SESSION="mysession" +playwright-cli open example.com # Uses "mysession" automatically +``` + +## Common Patterns + +### Concurrent Scraping + +```bash +#!/bin/bash +# Scrape multiple sites concurrently + +# Start all browsers +playwright-cli -s=site1 open https://site1.com & +playwright-cli -s=site2 open https://site2.com & +playwright-cli -s=site3 open https://site3.com & +wait + +# Take snapshots from each +playwright-cli -s=site1 snapshot +playwright-cli -s=site2 snapshot +playwright-cli -s=site3 snapshot + +# Cleanup +playwright-cli close-all +``` + +### A/B Testing Sessions + +```bash +# Test different user experiences +playwright-cli -s=variant-a open "https://app.com?variant=a" +playwright-cli -s=variant-b open "https://app.com?variant=b" + +# Compare +playwright-cli -s=variant-a screenshot +playwright-cli -s=variant-b screenshot +``` + +### Persistent Profile + +By default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk: + +```bash +# Use persistent profile (auto-generated location) +playwright-cli open https://example.com --persistent + +# Use persistent profile with custom directory +playwright-cli open https://example.com --profile=/path/to/profile +``` + +## Attaching to a Running Browser + +Use `attach` to connect to a browser that is already running, instead of launching a new one. + +### Attach by channel name + +Connect to a running Chrome or Edge instance by its channel name. The browser must have remote debugging enabled — navigate to `chrome://inspect/#remote-debugging` in the target browser and check "Allow remote debugging for this browser instance". + +```bash +# Attach to Chrome +playwright-cli attach --cdp=chrome + +# Attach to Chrome Canary +playwright-cli attach --cdp=chrome-canary + +# Attach to Microsoft Edge +playwright-cli attach --cdp=msedge + +# Attach to Edge Dev +playwright-cli attach --cdp=msedge-dev +``` + +Supported channels: `chrome`, `chrome-beta`, `chrome-dev`, `chrome-canary`, `msedge`, `msedge-beta`, `msedge-dev`, `msedge-canary`. + +When `--session` is not provided, the session is named after the channel (e.g. `--cdp=msedge` creates a session called `msedge`), so parallel attaches to Chrome and Edge don't collide on `default`. Pass `--session=` to override. + +### Attach via CDP endpoint + +Connect to a browser that exposes a Chrome DevTools Protocol endpoint: + +```bash +playwright-cli attach --cdp=http://localhost:9222 +``` + +### Attach via browser extension + +Connect to a browser with the Playwright extension installed: + +```bash +playwright-cli attach --extension +``` + +### Detach + +Tear down an attached session without affecting the external browser: + +```bash +# Detach the default attached session +playwright-cli detach + +# Detach a specific attached session +playwright-cli -s=msedge detach +``` + +`detach` only works on sessions created via `attach`. For sessions created via `open`, use `close`. + +## Default Browser Session + +When `-s` is omitted, commands use the default browser session: + +```bash +# These use the same default browser session +playwright-cli open https://example.com +playwright-cli snapshot +playwright-cli close # Stops default browser +``` + +## Browser Session Configuration + +Configure a browser session with specific settings when opening: + +```bash +# Open with config file +playwright-cli open https://example.com --config=.playwright/my-cli.json + +# Open with specific browser +playwright-cli open https://example.com --browser=firefox + +# Open in headed mode +playwright-cli open https://example.com --headed + +# Open with persistent profile +playwright-cli open https://example.com --persistent +``` + +## Best Practices + +### 1. Name Browser Sessions Semantically + +```bash +# GOOD: Clear purpose +playwright-cli -s=github-auth open https://github.com +playwright-cli -s=docs-scrape open https://docs.example.com + +# AVOID: Generic names +playwright-cli -s=s1 open https://github.com +``` + +### 2. Always Clean Up + +```bash +# Stop browsers when done +playwright-cli -s=auth close +playwright-cli -s=scrape close + +# Or stop all at once +playwright-cli close-all + +# If browsers become unresponsive or zombie processes remain +playwright-cli kill-all +``` + +### 3. Delete Stale Browser Data + +```bash +# Remove old browser data to free disk space +playwright-cli -s=oldsession delete-data +``` diff --git a/.claude/skills/playwright-cli/references/spec-driven-testing.md b/.claude/skills/playwright-cli/references/spec-driven-testing.md new file mode 100644 index 0000000000..70f05135eb --- /dev/null +++ b/.claude/skills/playwright-cli/references/spec-driven-testing.md @@ -0,0 +1,308 @@ +# Spec-driven testing (plan → generate → heal) + +End-to-end workflow for authoring and maintaining Playwright tests using `playwright-cli`. The three sections below can be used independently: + +- **Planning** — explore the app, produce a spec file describing what to test. +- **Generate** — turn a spec into Playwright test files. Update the spec if it's vague or stale. +- **Heal** — diagnose failing tests, fix the code, reconcile the spec with reality. + +All three lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics and [test-generation.md](test-generation.md) for how every `playwright-cli` action emits Playwright TypeScript. + +--- + +## 1. Planning + +Goal: produce a spec file (e.g. `specs/.plan.md`) that enumerates the scenarios to test. **Always** write the spec to a file. + +### 1.1 Prerequisite: workspace + +Check the workspace has Playwright installed before anything else: + +```bash +# Either of these confirms a workspace: +test -f playwright.config.ts || test -f playwright.config.js +npx --no-install playwright --version +``` + +If there is no Playwright install, bootstrap one and let the user pick the defaults: + +```bash +npm init playwright@latest +``` + +### 1.2 Prerequisite: seed test + +A **seed test** is a minimal test that lands the page in the state every scenario starts from: navigation to the app, any required login, feature flags, etc. Scenarios assume a fresh start _after_ the seed. `--debug=cli` pauses _inside_ this test, so the seed is where every planning and generation session begins. + +Minimum viable seed: + +```ts +// tests/seed.spec.ts +import { test } from "@playwright/test"; + +test("seed", async ({ page }) => { + await page.goto("https://example.com/"); +}); +``` + +Preferred — push navigation into a fixture so scenario tests reuse it: + +```ts +// tests/fixtures.ts +import { test as baseTest } from "@playwright/test"; +export { expect } from "@playwright/test"; + +export const test = baseTest.extend({ + page: async ({ page }, use) => { + await page.goto("https://example.com/"); + await use(page); + }, +}); +``` + +```ts +// tests/seed.spec.ts +import { test } from "./fixtures"; + +test("seed", async ({ page }) => { + // Fixture already navigates. This empty body tells agents where to start. +}); +``` + +If no seed exists, create one that at least navigates to the app. + +### 1.3 Explore the app + +Launch the app via the seed in the background and attach: + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test tests/seed.spec.ts --debug=cli +# wait for "Debugging Instructions" and the session name tw-XXXX +playwright-cli attach tw-XXXX +``` + +Resume so the seed runs, then probe the app: + +```bash +playwright-cli resume # resume so that seed test runs fully +playwright-cli snapshot # inventory of interactive elements +playwright-cli click e5 # follow a flow +playwright-cli eval "location.href" # read URL / state +playwright-cli show --annotate # ask the user to point at something +``` + +Map out: + +- Interactive surfaces (forms, buttons, lists, filters, modals). +- Primary user journeys end-to-end. +- Edge cases: empty states, validation errors, very long input, boundary values. +- Persistence: reload, local/session storage, URL fragments. +- Navigation: which controls change the URL, back/forward behaviour. + +**Important**: Do not just open the app url with playwright-cli, always go through the test to capture any custom setup done there. +**Important**: Stop the background test when done exploring. + +### 1.4 Write the spec file + +Save under `specs/.plan.md`. Use this structure: + +```markdown +# Test Plan + +## Application Overview + + + +## Test Scenarios + +### 1. + +**Seed:** `tests/seed.spec.ts` + +#### 1.1. + +**File:** `tests//.spec.ts` + +**Steps:** + +1. + - expect: + - expect: + +2. + - expect: + +#### 1.2. + +... + +### 2. + +**Seed:** `tests/seed.spec.ts` +... +``` + +Guidelines: + +- Each scenario is independent and starts from the seed's fresh state — never chain scenarios. +- Scenario names are kebab-case and match the test file name (`should-add-single-todo` → `should-add-single-todo.spec.ts`). +- Cover happy path, edge cases, validation, negative flows, persistence. +- Write steps at the user level ("Type 'Buy milk' into the input"), not the API level ("call `fill`"). +- Put observable outcomes in `- expect:` bullets; each becomes an assertion during generation. + +--- + +## 2. Generate + +Goal: take a spec file and produce Playwright test files. Optionally update the spec if it has drifted. + +### 2.1 Inputs + +- **Spec file**, e.g. `specs/basic-operations.plan.md`. +- **Target**: either a single scenario (e.g. `1.2`), a whole group (`1`), or all. +- **Seed file**, read from the `**Seed:**` line of the scenario's group. + +### 2.2 Generate one scenario + +For each target scenario, in sequence (never in parallel — scenarios share the seed session): + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli # background +playwright-cli attach tw-XXXX +# resume +``` + +**Do not** just open the app url with playwright-cli, always go through the test to capture any custom setup done there. + +Walk the scenario's `Steps:` one by one with `playwright-cli`, treating the spec as the plan and the live app as the source of truth. If a step is vague ("click the button" — which button?), references an element that no longer exists, or contradicts the app's actual behaviour, use your judgement: update the spec to match what the app really does, then keep going. Editing the spec mid-generation is expected. + +Every action prints the equivalent Playwright TypeScript (see [test-generation.md](test-generation.md)): + +```bash +playwright-cli snapshot # find refs +playwright-cli fill e3 "John Doe" # -> page.getByRole('textbox', {...}).fill(...) +playwright-cli press Enter +playwright-cli click e7 +``` + +For each `- expect:` bullet, add an explicit assertion. See [test-generation.md](test-generation.md) for details. + +Collect the generated code and write the test file at the path given in the spec: + +```ts +// spec: specs/basic-operations.plan.md +// seed: tests/seed.spec.ts +import { test, expect } from "./fixtures"; // or '@playwright/test' if no fixtures file + +test.describe("Singing in and out", () => { + test("should sign in", async ({ page }) => { + // 1. Navigate to the application + // (handled by the seed fixture) + + // 2. Type 'John Doe' into the username field + await page.getByRole("textbox", { name: "username" }).fill("John Doe"); + + // 3. Type password + await page.getByRole("textbox", { name: "password" }).fill("TestPassword"); + + // 4. Press Enter to submit + await page.getByRole("textbox", { name: "password" }).press("Enter"); + + await expect(page.getByRole("heading")).toContainText("Welcome, John Doe!"); + }); +}); +``` + +Rules: + +- **One test per file.** File path, describe name, and test name come verbatim from the spec (minus the ordinal). +- Prefix each numbered step with a `// N. ` comment before its actions. +- Use the describe group name verbatim from the spec (no `1.` ordinal). +- Import from `./fixtures` if the project has one; otherwise `@playwright/test`. +- **Important**: close the CLI session and stop the background test before moving to the next scenario. + +### 2.3 Generate multiple scenarios + +Loop 2.2 over the targeted scenarios one at a time, restarting the seed between each so every test starts from a clean page. This is safe to parallelise due to unique generated session names - just make sure each test run is stopped. + +### 2.4 Run generated tests + +After generation, run the new tests once: + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test tests//.spec.ts +``` + +Any failure goes to Section 3. + +--- + +## 3. Heal + +Goal: fix failing tests, and update the spec if the app's intended behaviour changed. + +### 3.1 Find failing tests + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test +``` + +Record the list of failing `:` entries and process them one at a time. Do not attempt parallel fixes — shared state and the single CLI session make that fragile. + +### 3.2 Debug one failure + +Run the single failing test in debug mode in the background, then attach: + +```bash +PLAYWRIGHT_HTML_OPEN=never npx playwright test tests//.spec.ts: --debug=cli +# wait for "Debugging Instructions" and the tw-XXXX session name +playwright-cli attach tw-XXXX +``` + +The test is paused at the start. Step forward or run to until just before the failing action or assertion, then diagnose: + +```bash +playwright-cli snapshot # did the element change / move / rename? +playwright-cli console # app-side errors? +playwright-cli network # failed request? wrong payload? +playwright-cli show --annotate # ask the user to point somewhere +``` + +Common causes: selector drift, new wrapper element, label/ARIA rename, timing (transition, async load), assertion text updated in the app, test data leaking between runs. + +Rehearse the corrected interaction with `playwright-cli` — the generated code in the output is what you paste back into the test. + +### 3.3 Apply the fix + +Edit the test file: update the locator, assertion, step order, or inputs to match the corrected behaviour. Stop the background debug run. Rerun the single test to confirm green. + +Never skip hooks or add sleeps as a fix. Never use `networkidle`. + +### 3.4 Reconcile with the spec + +Open the spec referenced by the `// spec:` header in the test file and locate the scenario that matches the test. + +- **Fix was purely technical** (locator drift, better assertion shape) and the spec's user-level behaviour still matches the app → leave the spec alone. +- **Fix changed user-visible steps, inputs, order, or expected outcomes** that the spec describes → update the spec to match reality. Keep the scenario id and file path stable; only the step / expect lines change. +- **Unclear whether the app change is intentional** (spec is stale) **or a regression** (test was right, app is wrong) → **stop and ask the user**. Provide: + - the scenario id (e.g. `2.3`), + - the spec lines that no longer match, + - the observed app behaviour (quote a snapshot excerpt or a concrete outcome). + +Only after the user answers, either update the spec (intentional change) or file/flag the test as covering a bug (regression). + +### 3.5 Iteration and giving up + +- Fix failures one at a time; rerun after each. +- If after thorough investigation you are confident the test is correct but the app is wrong _and_ the user has confirmed it's a bug: mark the test `test.fixme(...)` with a comment pointing at the user's decision or issue link. Never silently skip. + +--- + +## Cross-references + +| For... | See | +| ---------------------------------------------- | ---------------------------------------------- | +| `--debug=cli` / attach mechanics | [playwright-tests.md](playwright-tests.md) | +| How `playwright-cli` actions become TS | [test-generation.md](test-generation.md) | +| Mocking requests during exploration/generation | [request-mocking.md](request-mocking.md) | +| Managing the CLI browser session | [session-management.md](session-management.md) | diff --git a/.claude/skills/playwright-cli/references/storage-state.md b/.claude/skills/playwright-cli/references/storage-state.md new file mode 100644 index 0000000000..c856db5e40 --- /dev/null +++ b/.claude/skills/playwright-cli/references/storage-state.md @@ -0,0 +1,275 @@ +# Storage Management + +Manage cookies, localStorage, sessionStorage, and browser storage state. + +## Storage State + +Save and restore complete browser state including cookies and storage. + +### Save Storage State + +```bash +# Save to auto-generated filename (storage-state-{timestamp}.json) +playwright-cli state-save + +# Save to specific filename +playwright-cli state-save my-auth-state.json +``` + +### Restore Storage State + +```bash +# Load storage state from file +playwright-cli state-load my-auth-state.json + +# Reload page to apply cookies +playwright-cli open https://example.com +``` + +### Storage State File Format + +The saved file contains: + +```json +{ + "cookies": [ + { + "name": "session_id", + "value": "abc123", + "domain": "example.com", + "path": "/", + "expires": 1735689600, + "httpOnly": true, + "secure": true, + "sameSite": "Lax" + } + ], + "origins": [ + { + "origin": "https://example.com", + "localStorage": [ + { "name": "theme", "value": "dark" }, + { "name": "user_id", "value": "12345" } + ] + } + ] +} +``` + +## Cookies + +### List All Cookies + +```bash +playwright-cli cookie-list +``` + +### Filter Cookies by Domain + +```bash +playwright-cli cookie-list --domain=example.com +``` + +### Filter Cookies by Path + +```bash +playwright-cli cookie-list --path=/api +``` + +### Get Specific Cookie + +```bash +playwright-cli cookie-get session_id +``` + +### Set a Cookie + +```bash +# Basic cookie +playwright-cli cookie-set session abc123 + +# Cookie with options +playwright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax + +# Cookie with expiration (Unix timestamp) +playwright-cli cookie-set remember_me token123 --expires=1735689600 +``` + +### Delete a Cookie + +```bash +playwright-cli cookie-delete session_id +``` + +### Clear All Cookies + +```bash +playwright-cli cookie-clear +``` + +### Advanced: Multiple Cookies or Custom Options + +For complex scenarios like adding multiple cookies at once, use `run-code`: + +```bash +playwright-cli run-code "async page => { + await page.context().addCookies([ + { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true }, + { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' } + ]); +}" +``` + +## Local Storage + +### List All localStorage Items + +```bash +playwright-cli localstorage-list +``` + +### Get Single Value + +```bash +playwright-cli localstorage-get token +``` + +### Set Value + +```bash +playwright-cli localstorage-set theme dark +``` + +### Set JSON Value + +```bash +playwright-cli localstorage-set user_settings '{"theme":"dark","language":"en"}' +``` + +### Delete Single Item + +```bash +playwright-cli localstorage-delete token +``` + +### Clear All localStorage + +```bash +playwright-cli localstorage-clear +``` + +### Advanced: Multiple Operations + +For complex scenarios like setting multiple values at once, use `run-code`: + +```bash +playwright-cli run-code "async page => { + await page.evaluate(() => { + localStorage.setItem('token', 'jwt_abc123'); + localStorage.setItem('user_id', '12345'); + localStorage.setItem('expires_at', Date.now() + 3600000); + }); +}" +``` + +## Session Storage + +### List All sessionStorage Items + +```bash +playwright-cli sessionstorage-list +``` + +### Get Single Value + +```bash +playwright-cli sessionstorage-get form_data +``` + +### Set Value + +```bash +playwright-cli sessionstorage-set step 3 +``` + +### Delete Single Item + +```bash +playwright-cli sessionstorage-delete step +``` + +### Clear sessionStorage + +```bash +playwright-cli sessionstorage-clear +``` + +## IndexedDB + +### List Databases + +```bash +playwright-cli run-code "async page => { + return await page.evaluate(async () => { + const databases = await indexedDB.databases(); + return databases; + }); +}" +``` + +### Delete Database + +```bash +playwright-cli run-code "async page => { + await page.evaluate(() => { + indexedDB.deleteDatabase('myDatabase'); + }); +}" +``` + +## Common Patterns + +### Authentication State Reuse + +```bash +# Step 1: Login and save state +playwright-cli open https://app.example.com/login +playwright-cli snapshot +playwright-cli fill e1 "user@example.com" +playwright-cli fill e2 "password123" +playwright-cli click e3 + +# Save the authenticated state +playwright-cli state-save auth.json + +# Step 2: Later, restore state and skip login +playwright-cli state-load auth.json +playwright-cli open https://app.example.com/dashboard +# Already logged in! +``` + +### Save and Restore Roundtrip + +```bash +# Set up authentication state +playwright-cli open https://example.com +playwright-cli eval "() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }" + +# Save state to file +playwright-cli state-save my-session.json + +# ... later, in a new session ... + +# Restore state +playwright-cli state-load my-session.json +playwright-cli open https://example.com +# Cookies and localStorage are restored! +``` + +## Security Notes + +- Never commit storage state files containing auth tokens +- Add `*.auth-state.json` to `.gitignore` +- Delete state files after automation completes +- Use environment variables for sensitive data +- By default, sessions run in-memory mode which is safer for sensitive operations diff --git a/.claude/skills/playwright-cli/references/test-generation.md b/.claude/skills/playwright-cli/references/test-generation.md new file mode 100644 index 0000000000..9ce1fe5ad2 --- /dev/null +++ b/.claude/skills/playwright-cli/references/test-generation.md @@ -0,0 +1,138 @@ +# Test Generation + +Generate Playwright test code automatically as you interact with the browser. + +## How It Works + +Every action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. +This code appears in the output and can be copied directly into your test files. + +## Example Workflow + +```bash +# Start a session +playwright-cli open https://example.com/login + +# Take a snapshot to see elements +playwright-cli snapshot +# Output shows: e1 [textbox "Email"], e2 [textbox "Password"], e3 [button "Sign In"] + +# Fill form fields - generates code automatically +playwright-cli fill e1 "user@example.com" +# Ran Playwright code: +# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com'); + +playwright-cli fill e2 "password123" +# Ran Playwright code: +# await page.getByRole('textbox', { name: 'Password' }).fill('password123'); + +playwright-cli click e3 +# Ran Playwright code: +# await page.getByRole('button', { name: 'Sign In' }).click(); +``` + +## Building a Test File + +Collect the generated code into a Playwright test: + +```typescript +import { test, expect } from "@playwright/test"; + +test("login flow", async ({ page }) => { + // Generated code from playwright-cli session: + await page.goto("https://example.com/login"); + await page.getByRole("textbox", { name: "Email" }).fill("user@example.com"); + await page.getByRole("textbox", { name: "Password" }).fill("password123"); + await page.getByRole("button", { name: "Sign In" }).click(); + + // Add assertions + await expect(page).toHaveURL(/.*dashboard/); +}); +``` + +## Best Practices + +### 1. Use Semantic Locators + +The generated code uses role-based locators when possible, which are more resilient: + +```typescript +// Generated (good - semantic) +await page.getByRole("button", { name: "Submit" }).click(); + +// Avoid (fragile - CSS selectors) +await page.locator("#submit-btn").click(); +``` + +### 2. Explore Before Recording + +Take snapshots to understand the page structure before recording actions: + +```bash +playwright-cli open https://example.com +playwright-cli snapshot +# Review the element structure +playwright-cli click e5 +``` + +### 3. Add Assertions Manually + +Generated code captures actions but not assertions. Add expectations in your test using one of the recommended matchers: + +- `toBeVisible()` — element is rendered and visible +- `toHaveText(text)` — element text content matches +- `toHaveValue(value) / toBeEmpty()` — input/select value matches +- `toBeChecked() / toBeUnchecked()` — checkbox state matches +- `toMatchAriaSnapshot(snapshot)` — page (or locator) matches a partial accessibility snapshot + +Use `playwright-cli generate-locator ` to produce the locator expression for the assertion, and the snapshot/eval commands to capture the expected value. + +When asserting text content, make sure that generated locator does not contain text from the element itself. `getByTestId()` or `getByLabel()` usually work well with asserting text. When locator is text-based, prefer `toBeVisible()` instead. + +Snapshot to be matched does not have to contain all the information - only capture what's necessary for the assertion. You can use regular expressions for unstable values. + +```bash +# Get a stable locator for an element ref to use in the assertion +playwright-cli --raw generate-locator e5 +# getByRole('button', { name: 'Submit' }) + +# Capture expected text content for toHaveText +playwright-cli --raw eval "el => el.textContent" e5 + +# Capture expected input value for toHaveValue/toBeEmpty +playwright-cli --raw eval "el => el.value" e5 + +# Capture expected aria snapshot for toMatchAriaSnapshot/toBeChecked +# (whole page, or use a ref to scope to a region) +playwright-cli --raw snapshot +playwright-cli --raw snapshot e5 +``` + +```typescript +// Generated action +await page.getByRole("button", { name: "Submit" }).click(); + +// Manual assertions using the outputs above: +await expect(page.getByRole("alert", { name: "Success" })).toBeVisible(); +await expect(page.getByTestId("main-header")).toHaveText("Welcome, user"); +await expect(page.getByRole("textbox", { name: "Email" })).toHaveValue( + "user@example.com", +); +await expect( + page.getByRole("checkbox", { name: "Enable notifications" }), +).toBeChecked(); + +// toMatchAriaSnapshot on the whole page, finds a matching region +await expect(page).toMatchAriaSnapshot(` + - heading "Welcome, user" + - link /\\d+ new messages?/ + - button "Sign out" +`); + +// toMatchAriaSnapshot scoped to a region +await expect(page.getByRole("navigation")).toMatchAriaSnapshot(` + - link "Home" + - link /\\d+ new messages?/ + - link "Profile" +`); +``` diff --git a/.claude/skills/playwright-cli/references/tracing.md b/.claude/skills/playwright-cli/references/tracing.md new file mode 100644 index 0000000000..d81cdeb12b --- /dev/null +++ b/.claude/skills/playwright-cli/references/tracing.md @@ -0,0 +1,142 @@ +# Tracing + +Capture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs. + +## Basic Usage + +```bash +# Start trace recording +playwright-cli tracing-start + +# Perform actions +playwright-cli open https://example.com +playwright-cli click e1 +playwright-cli fill e2 "test" + +# Stop trace recording +playwright-cli tracing-stop +``` + +## Trace Output Files + +When you start tracing, Playwright creates a `traces/` directory with several files: + +### `trace-{timestamp}.trace` + +**Action log** - The main trace file containing: + +- Every action performed (clicks, fills, navigations) +- DOM snapshots before and after each action +- Screenshots at each step +- Timing information +- Console messages +- Source locations + +### `trace-{timestamp}.network` + +**Network log** - Complete network activity: + +- All HTTP requests and responses +- Request headers and bodies +- Response headers and bodies +- Timing (DNS, connect, TLS, TTFB, download) +- Resource sizes +- Failed requests and errors + +### `resources/` + +**Resources directory** - Cached resources: + +- Images, fonts, stylesheets, scripts +- Response bodies for replay +- Assets needed to reconstruct page state + +## What Traces Capture + +| Category | Details | +| --------------- | -------------------------------------------------- | +| **Actions** | Clicks, fills, hovers, keyboard input, navigations | +| **DOM** | Full DOM snapshot before/after each action | +| **Screenshots** | Visual state at each step | +| **Network** | All requests, responses, headers, bodies, timing | +| **Console** | All console.log, warn, error messages | +| **Timing** | Precise timing for each operation | + +## Use Cases + +### Debugging Failed Actions + +```bash +playwright-cli tracing-start +playwright-cli open https://app.example.com + +# This click fails - why? +playwright-cli click e5 + +playwright-cli tracing-stop +# Open trace to see DOM state when click was attempted +``` + +### Analyzing Performance + +```bash +playwright-cli tracing-start +playwright-cli open https://slow-site.com +playwright-cli tracing-stop + +# View network waterfall to identify slow resources +``` + +### Capturing Evidence + +```bash +# Record a complete user flow for documentation +playwright-cli tracing-start + +playwright-cli open https://app.example.com/checkout +playwright-cli fill e1 "4111111111111111" +playwright-cli fill e2 "12/25" +playwright-cli fill e3 "123" +playwright-cli click e4 + +playwright-cli tracing-stop +# Trace shows exact sequence of events +``` + +## Trace vs Video vs Screenshot + +| Feature | Trace | Video | Screenshot | +| ----------------------- | ----------- | ----------- | ---------------- | +| **Format** | .trace file | .webm video | .png/.jpeg image | +| **DOM inspection** | Yes | No | No | +| **Network details** | Yes | No | No | +| **Step-by-step replay** | Yes | Continuous | Single frame | +| **File size** | Medium | Large | Small | +| **Best for** | Debugging | Demos | Quick capture | + +## Best Practices + +### 1. Start Tracing Before the Problem + +```bash +# Trace the entire flow, not just the failing step +playwright-cli tracing-start +playwright-cli open https://example.com +# ... all steps leading to the issue ... +playwright-cli tracing-stop +``` + +### 2. Clean Up Old Traces + +Traces can consume significant disk space: + +```bash +# Remove traces older than 7 days +find .playwright-cli/traces -mtime +7 -delete +``` + +## Limitations + +- Traces add overhead to automation +- Large traces can consume significant disk space +- Some dynamic content may not replay perfectly diff --git a/.claude/skills/playwright-cli/references/video-recording.md b/.claude/skills/playwright-cli/references/video-recording.md new file mode 100644 index 0000000000..dd1f9e8bce --- /dev/null +++ b/.claude/skills/playwright-cli/references/video-recording.md @@ -0,0 +1,157 @@ +# Video Recording + +Capture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec). + +## Basic Recording + +```bash +# Open browser first +playwright-cli open + +# Start recording +playwright-cli video-start demo.webm + +# Add a chapter marker for section transitions +playwright-cli video-chapter "Getting Started" --description="Opening the homepage" --duration=2000 + +# Navigate and perform actions +playwright-cli goto https://example.com +playwright-cli snapshot +playwright-cli click e1 + +# Add another chapter +playwright-cli video-chapter "Filling Form" --description="Entering test data" --duration=2000 +playwright-cli fill e2 "test input" + +# Stop and save +playwright-cli video-stop +``` + +## Best Practices + +### 1. Use Descriptive Filenames + +```bash +# Include context in filename +playwright-cli video-start recordings/login-flow-2024-01-15.webm +playwright-cli video-start recordings/checkout-test-run-42.webm +``` + +### 2. Record entire hero scripts. + +When recording a video for the user or as a proof of work, it is best to create a code snippet and execute it with run-code. +It allows pulling appropriate pauses between the actions and annotating the video. There are new Playwright APIs for that. + +1. Perform scenario using CLI and take note of all locators and actions. You'll need those locators to request their bounding boxes for highlight. +2. Create a file with the intended script for video (below). Use pressSequentially w/ delay for nice typing, make reasonable pauses. +3. Use playwright-cli run-code --filename your-script.js + +**Important**: Overlays are `pointer-events: none` — they do not interfere with page interactions. You can safely keep sticky overlays visible while clicking, filling, or performing any actions on the page. + +```js +async (page) => { + await page.screencast.start({ + path: "video.webm", + size: { width: 1280, height: 800 }, + }); + await page.goto("https://demo.playwright.dev/todomvc"); + + // Show a chapter card — blurs the page and shows a dialog. + // Blocks until duration expires, then auto-removes. + // Use this for simple use cases, but always feel free to hand-craft your own beautiful + // overlay via await page.screencast.showOverlay(). + await page.screencast.showChapter("Adding Todo Items", { + description: "We will add several items to the todo list.", + duration: 2000, + }); + + // Perform action + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .pressSequentially("Walk the dog", { delay: 60 }); + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .press("Enter"); + await page.waitForTimeout(1000); + + // Show next chapter + await page.screencast.showChapter("Verifying Results", { + description: "Checking the item appeared in the list.", + duration: 2000, + }); + + // Add a sticky annotation that stays while you perform actions. + // Overlays are pointer-events: none, so they won't block clicks. + const annotation = await page.screencast.showOverlay(` +
+ ✓ Item added successfully +
+ `); + + // Perform more actions while the annotation is visible + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .pressSequentially("Buy groceries", { delay: 60 }); + await page + .getByRole("textbox", { name: "What needs to be done?" }) + .press("Enter"); + await page.waitForTimeout(1500); + + // Remove the annotation when done + await annotation.dispose(); + + // You can also highlight relevant locators and provide contextual annotations. + const bounds = await page.getByText("Walk the dog").boundingBox(); + await page.screencast.showOverlay( + ` +
+
+
Check it out, it is right above this text +
+ `, + { duration: 2000 }, + ); + + await page.screencast.stop(); +}; +``` + +Embrace creativity, overlays are powerful. + +### Overlay API Summary + +| Method | Use Case | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| `page.screencast.showChapter(title, { description?, duration?, styleSheet? })` | Full-screen chapter card with blurred backdrop — ideal for section transitions | +| `page.screencast.showOverlay(html, { duration? })` | Custom HTML overlay — use for callouts, labels, highlights | +| `disposable.dispose()` | Remove a sticky overlay added without duration | +| `page.screencast.hideOverlays()` / `page.screencast.showOverlays()` | Temporarily hide/show all overlays | + +## Tracing vs Video + +| Feature | Video | Tracing | +| -------- | -------------------- | ---------------------------------------- | +| Output | WebM file | Trace file (viewable in Trace Viewer) | +| Shows | Visual recording | DOM snapshots, network, console, actions | +| Use case | Demos, documentation | Debugging, analysis | +| Size | Larger | Smaller | + +## Limitations + +- Recording adds slight overhead to automation +- Large recordings can consume significant disk space diff --git a/.claude/skills/testing-skill/SKILL.md b/.claude/skills/testing-skill/SKILL.md new file mode 100644 index 0000000000..ab70133c11 --- /dev/null +++ b/.claude/skills/testing-skill/SKILL.md @@ -0,0 +1,62 @@ +--- +name: testing-skill +description: Instructions for writing, running, and updating unit/end-to-end tests. Should be used when prompted specifically to add tests for a given feature, bug, or regression. +--- + +# Testing + +In most cases, once a feature, bug fix, or other modification has been written, it will need to have tests added, or existing tests updated. + +## Test File Locations + +### Unit Tests + +`/tests/src/unit`: Contains the bulk of unit tests, mainly relating to interoperability between BlockNote's JSON format and HTML/Markdown. Also includes some miscellaneous tests, like React rendering, selection handling, and NextJS integration. + +`/packages/core/src/api`: Contains mainly tests for getting, inserting, updating, and removing blocks, etc, under `/blockManipulation/commands`. Also includes tests for intermediary functionality between BlockNote and the underlying TipTap editor, like converting between blocks & nodes, or setting editor event handlers. + +`/packages/xl-*`: Contain tests for functionality included in a given `xl-*` package. + +### 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. + +## When & How to Add Tests + +In general, we expect a change in code to result in failing test cases. If this does not happen, tests should be added and checked to ensure they pass with the code changes while failing without them. + +However, this may not be true when adding edge case handling or a new feature, where existing tests may all continue to pass. In this case, tests should be added as necessary to cover all of the new functionality. We should still ensure that the new tests pass with the new code changes while failing without them. + +We want to avoid adding end-to-end tests where it's possible to use unit tests instead. + +## Running & Updating Tests + +### Unit Tests + +Unit tests can be run from the root directory using `vp run test`, which will run all of them across all directories. A specific test file may be targeted by appending its name, i.e. `vp run test fileName`. Individual tests in a file may be disabled using `skip`, i.e. `it.skip("Test name", ...)` (remember to revert this once all tests pass). + +Updating tests can be done by adding the `-u` argument, i.e. `vp run test -u`. All of the other things you can do to scope which tests to target still apply. + +### 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. + +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 +``` + +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). + +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. + +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. + +When testing a visual change, prefer writing screenshots to verify that the change is working as expected. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..87697e5b29 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Keep the e2e image build context lean and Linux-clean: never copy host +# (macOS) node_modules or build outputs — deps are installed fresh inside the +# image. What actually lands in the image is selected explicitly by the COPY +# steps in tests/Dockerfile (manifests + example apps), not by exclusions here; +# these entries just stop the heavy/irrelevant trees from bloating the context. +**/node_modules +**/dist +**/types +**/.vite +**/.vite-plus +**/*.tsbuildinfo +**/.DS_Store +.git +**/test-results +**/blob-report +**/playwright-report +tests/.vitest-attachments diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index e9511bbf61..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "root": true, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "react-app", - "react-app/jest" - ], - "parser": "@typescript-eslint/parser", - "plugins": ["import", "@typescript-eslint"], - "settings": { - "import/extensions": [".ts", ".cts", ".mts", ".tsx", ".js", ".jsx"], - "import/external-module-folders": ["node_modules", "node_modules/@types"], - "import/parsers": { - "@typescript-eslint/parser": [".ts", ".cts", ".mts", ".tsx"] - }, - "import/resolver": { - "node": { - "extensions": [".ts", ".cts", ".mts", ".tsx", ".js", ".jsx"] - } - } - }, - "ignorePatterns": ["**/ui/*"], - "rules": { - "no-console": "error", - "curly": 1, - "import/extensions": ["error", "always", { "ignorePackages": true }], - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": true, - "peerDependencies": true, - "optionalDependencies": false, - "bundledDependencies": false - } - ], - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/ban-ts-comment": "off", - "import/no-cycle": "error" - } -} diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f258f05126..d431145bc6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,4 +3,4 @@ blank_issues_enabled: false contact_links: - name: Share an idea or suggest an enhancement url: https://github.com/TypeCellOS/BlockNote/discussions/categories/ideas-enhancements - about: Share feature ideas, enhancement suggestions, or other ideas for the BlockNote project. \ No newline at end of file + about: Share feature ideas, enhancement suggestions, or other ideas for the BlockNote project. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1b78160209..4cd39c8b21 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,14 +21,13 @@ updates: - dependency-name: "@tiptap/extension-code" - dependency-name: "@tiptap/extension-horizontal-rule" - dependency-name: "@tiptap/extension-italic" - - dependency-name: "@tiptap/extension-link" + - dependency-name: "@tiptap/extension-paragraph" - dependency-name: "@tiptap/extension-strike" - dependency-name: "@tiptap/extension-text" - dependency-name: "@tiptap/extension-underline" # prosemirror packages - dependency-name: "prosemirror-changeset" - - dependency-name: "prosemirror-dropcursor" - dependency-name: "prosemirror-highlight" - dependency-name: "prosemirror-model" - dependency-name: "prosemirror-state" @@ -41,6 +40,12 @@ updates: # yjs packages - dependency-name: "yjs" - dependency-name: "y-prosemirror" + ignore: + # Hono packages are used only in the demo AI server and are not part of + # the main editor/runtime surface area. + - dependency-name: "hono" + - dependency-name: "@hono/node-server" + - dependency-name: "@hono/*" groups: editor-dependencies: patterns: @@ -50,3 +55,14 @@ updates: - "react-dom" - "yjs" - "y-prosemirror" + cooldown: + default-days: 7 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci" + include: "scope" + cooldown: + default-days: 7 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa013cc9a2..2f8f7dfc38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,9 +6,11 @@ on: pull_request: types: [opened, synchronize, reopened, edited] +permissions: + contents: read + env: - NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN }} - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_SERVER }} + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: build: @@ -16,106 +18,184 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 100 + persist-credentials: false - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - uses: nrwl/nx-set-shas@v3 - - - uses: actions/setup-node@v4 + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 with: - cache: "pnpm" - cache-dependency-path: "**/pnpm-lock.yaml" - node-version-file: ".nvmrc" + node-version-file: ".node-version" + cache: true - - name: Cache NX - uses: actions/cache@v4 - with: - path: .nx/cache - key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} - restore-keys: | - nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}- - nx-${{ env.NX_BRANCH }}- - nx- - - # This is needed for the canvas dep, Tiptap V3 should remove the need for this - - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - name: Install Dependencies - run: pnpm install + run: vp install - name: Lint packages - run: pnpm run lint + run: vp lint - name: Build packages - run: pnpm run build + run: vp run -r build - name: Run unit tests - run: pnpm run test + run: vp run -r test + + - name: Run Next.js integration test (production build) + run: NEXTJS_TEST_MODE=build vp test run src/unit/nextjs/serverUtil.test.ts + working-directory: tests - name: Upload webpack stats artifact (editor) - uses: relative-ci/agent-upload-artifact-action@v2 + uses: relative-ci/agent-upload-artifact-action@a2b5741b4f7e6a989c84ec1a3059696b23c152e5 # v2 with: webpackStatsFile: ./playground/dist/webpack-stats.json artifactName: relative-ci-artifacts-editor - name: Soft release id: soft-release - run: pnpx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact - - playwright: - name: "Playwright Tests - ${{ matrix.browser }}" + run: vp dlx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact + + e2e: + # Vitest Browser Mode runs in the Playwright Linux container — the same + # environment as the local Docker run — so behaviour matches local dev. + # The suite resolves every `@blocknote/*` import to its `src/` via the + # aliases in `tests/vite.config.browser.ts` (vite transpiles the package + # sources on the fly), so the packages do NOT need to be built to `dist` + # first — `vp install` + the checked-out sources are enough. + name: "E2E - ${{ matrix.browser }} (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})" runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 30 container: - image: mcr.microsoft.com/playwright:v1.51.1-noble + image: mcr.microsoft.com/playwright:v1.60.0-noble strategy: fail-fast: false matrix: browser: [chromium, firefox, webkit] + shardIndex: [1, 2] + shardTotal: [2] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 100 + persist-credentials: false - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - uses: nrwl/nx-set-shas@v3 + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + cache: true - - uses: actions/setup-node@v4 + - name: Install dependencies + run: vp install + + # No preview server: Vitest Browser Mode serves the tests + mounted example + # apps itself. `--browser` selects this matrix job's browser and + # `--shard=/` splits that browser's test files across two + # parallel machines. Each shard records a machine-readable `blob` report + # (named per browser+shard so they don't collide); the `merge-reports` job + # stitches every browser's shards into one HTML report afterwards. + - name: Run e2e tests (${{ matrix.browser }} ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) + run: HOME=/root vp test -c vite.config.browser.ts --run --browser ${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=default --reporter=blob --outputFile.blob=blob-report/blob-${{ matrix.browser }}-${{ matrix.shardIndex }}.json + working-directory: tests + + - name: Upload blob report (${{ matrix.browser }} ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} with: - cache: "pnpm" - cache-dependency-path: "**/pnpm-lock.yaml" - node-version-file: ".nvmrc" + name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/blob-report/ + retention-days: 1 - - name: Cache NX - uses: actions/cache@v4 + - name: Upload failure artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ failure() }} + with: + name: e2e-attachments-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/.vitest-attachments/ + retention-days: 7 + + merge-reports: + # Stitch every browser+shard blob report into a single navigable HTML + # report (the Vitest equivalent of `playwright merge-reports`). Runs even + # when a shard failed, so the report always covers every browser. + name: "E2E Report" + runs-on: ubuntu-latest + needs: e2e + if: ${{ !cancelled() }} + timeout-minutes: 15 + # Same container as the e2e shards: `--mergeReports` doesn't run tests or + # launch browsers, but the HTML reporter still resolves the test files' + # browser environment, so the browser config must stay enabled (and present). + container: + image: mcr.microsoft.com/playwright:v1.60.0-noble + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: - path: .nx/cache - key: nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} - restore-keys: | - nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}- - nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}- - nx- + fetch-depth: 100 + persist-credentials: false - - run: apt-get update && apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + cache: true - name: Install dependencies - run: pnpm install + run: vp install - - name: Build packages - run: pnpm run build - - - name: Run server and Playwright tests - run: | - HOME=/root PLAYWRIGHT_CONFIG="--project ${{ matrix.browser }}" pnpm run e2e - - - uses: actions/upload-artifact@v4 - if: always() + # Gather every shard's blob into one directory (blob-report-chromium-1, …). + - name: Download blob reports + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + path: tests/blob-report + pattern: blob-report-* + merge-multiple: true + + # Re-emit a single HTML report from the merged blobs (no tests are run). + - name: Merge into HTML report + run: HOME=/root vp test -c vite.config.browser.ts --mergeReports=blob-report --reporter=html + working-directory: tests + + - name: Upload merged HTML report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} with: - name: playwright-report-${{ matrix.browser }} + name: e2e-report path: tests/playwright-report/ - retention-days: 30 + retention-days: 7 + + deploy-report: + # Publish the merged report to a GitHub Pages PR preview and comment the + # link on the PR (Vercel-style). Pages serves it over HTTP, so the report's + # data loads correctly — unlike opening the downloaded artifact over file:// + # (the @vitest/ui report fetches its data and is blocked by CORS there). + # + # Default `needs` semantics: this runs only when `merge-reports` succeeded + # (i.e. a report exists) — but regardless of whether the tests passed, so a + # red run still gets a navigable preview. Skipped for fork PRs, whose + # read-only token can't push to gh-pages or comment (they keep the artifact). + name: "E2E Report Preview" + runs-on: ubuntu-latest + needs: merge-reports + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Download merged report + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: e2e-report + path: e2e-report + + # Deploys to the `gh-pages` branch under `pr-preview/pr-/` and posts a + # sticky comment with the URL. The companion `pr-preview-cleanup` workflow + # removes it when the PR closes. + - name: Deploy report to PR preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1 + with: + source-dir: e2e-report + preview-branch: gh-pages + umbrella-dir: pr-preview + action: deploy diff --git a/.github/workflows/fresh-install-tests.yml b/.github/workflows/fresh-install-tests.yml new file mode 100644 index 0000000000..a1b441678d --- /dev/null +++ b/.github/workflows/fresh-install-tests.yml @@ -0,0 +1,148 @@ +name: Fresh Install Tests + +# Periodically tests BlockNote with the latest versions of its production +# dependencies (within declared semver ranges). This catches breakage when a +# new release of a dep like @tiptap/* or prosemirror-* ships and conflicts +# with BlockNote's declared ranges — the kind of failure a user would hit when +# running `npm install @blocknote/react` in a fresh project. +# +# Only production dependencies of published (non-private) packages are updated. +# DevDependencies (vitest, vite, typescript, etc.) stay pinned to the lockfile, +# so test tooling churn doesn't cause false positives. + +on: + schedule: + - cron: "0 2 * * *" # Daily at 02:00 UTC + workflow_dispatch: # Allow manual runs + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + fresh-install-unit-tests: + name: Unit Tests (Fresh Dep Resolution) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - id: checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - id: setup_vp + uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + # Intentionally no install cache — we want fresh prod dep resolution. + cache: false + + - id: install_dependencies + name: Install dependencies + run: vp install + + - id: update_prod_deps + name: Update prod deps of published packages + # Resolves production dependencies of every published (non-private) + # workspace package to the latest version within their declared semver + # ranges. This simulates what a user gets when running + # `npm install @blocknote/react` in a fresh project. + # DevDependencies are left at their lockfile versions. + # NB: this uses pnpm directly because vp doesn't expose a `--prod`-only + # update flow; setup-vp installs pnpm on PATH so this still works. + run: | + FILTERS=$(node -e " + const fs = require('fs'); + const path = require('path'); + fs.readdirSync('packages').forEach(dir => { + try { + const pkg = JSON.parse(fs.readFileSync(path.join('packages', dir, 'package.json'), 'utf8')); + if (!pkg.private && pkg.name) process.stdout.write('--filter ' + pkg.name + ' '); + } catch {} + }); + ") + echo "Updating prod deps for: $FILTERS" + eval pnpm update --prod $FILTERS + + - id: dedupe_deps + name: Dedupe transitive dependencies + # After bumping the publishable packages' prod deps, collapse any + # duplicate transitive resolutions (e.g. @tiptap/core + @tiptap/pm) + # that would otherwise differ between the updated publishable packages + # and the un-updated examples/playground. Without this, TypeScript + # treats the two copies' exports as unrelated types and example-editor + # fails to build (TS2322 on Extension vs AnyExtension). + # Dedupe only rewrites the lockfile — it does NOT modify package.json, + # so the examples' "@blocknote/*": "latest" specs (which is what + # CodeSandbox users see) stay intact. + run: pnpm dedupe + + - id: build_packages + name: Build packages + run: vp run -r build + + - id: run_unit_tests + name: Run unit tests + run: vp run -r test + + - name: Notify Slack on workflow failure + if: ${{ failure() }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + REPOSITORY: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + RUN_ID: ${{ github.run_id }} + RUN_NUMBER: ${{ github.run_number }} + RUN_ATTEMPT: ${{ github.run_attempt }} + BRANCH: ${{ github.ref_name }} + STEPS_CHECKOUT_OUTCOME: ${{ steps.checkout.outcome }} + STEPS_SETUP_VP_OUTCOME: ${{ steps.setup_vp.outcome }} + STEPS_INSTALL_DEPENDENCIES_OUTCOME: ${{ steps.install_dependencies.outcome }} + STEPS_UPDATE_PROD_DEPS_OUTCOME: ${{ steps.update_prod_deps.outcome }} + STEPS_DEDUPE_DEPS_OUTCOME: ${{ steps.dedupe_deps.outcome }} + STEPS_BUILD_PACKAGES_OUTCOME: ${{ steps.build_packages.outcome }} + STEPS_RUN_UNIT_TESTS_OUTCOME: ${{ steps.run_unit_tests.outcome }} + run: | + if [ -z "$SLACK_WEBHOOK_URL" ]; then + echo "SLACK_WEBHOOK_URL is not configured; skipping Slack notification." + exit 0 + fi + + failed_step="Unknown step" + if [ "${STEPS_CHECKOUT_OUTCOME}" = "failure" ]; then + failed_step="Checkout repository" + elif [ "${STEPS_SETUP_VP_OUTCOME}" = "failure" ]; then + failed_step="Setup Vite+" + elif [ "${STEPS_INSTALL_DEPENDENCIES_OUTCOME}" = "failure" ]; then + failed_step="Install dependencies" + elif [ "${STEPS_UPDATE_PROD_DEPS_OUTCOME}" = "failure" ]; then + failed_step="Update prod deps of published packages" + elif [ "${STEPS_DEDUPE_DEPS_OUTCOME}" = "failure" ]; then + failed_step="Dedupe transitive dependencies" + elif [ "${STEPS_BUILD_PACKAGES_OUTCOME}" = "failure" ]; then + failed_step="Build packages" + elif [ "${STEPS_RUN_UNIT_TESTS_OUTCOME}" = "failure" ]; then + failed_step="Run unit tests" + fi + + run_url="https://github.com/${REPOSITORY}/actions/runs/${RUN_ID}" + message=$(printf '%s\n%s\n%s\n%s' \ + ":warning: Fresh Install Tests failed in *${REPOSITORY}* on branch *${BRANCH}*." \ + "*Workflow:* ${WORKFLOW}" \ + "*Run:* <${run_url}|#${RUN_NUMBER} (attempt ${RUN_ATTEMPT})>" \ + "*Failed step:* ${failed_step}") + payload=$(jq --compact-output --null-input --arg text "$message" '{text: $text}') + + curl -sS -X POST \ + --fail \ + --retry 4 \ + --retry-all-errors \ + --retry-max-time 60 \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Content-type: application/json" \ + --data "$payload" \ + "$SLACK_WEBHOOK_URL" diff --git a/.github/workflows/pr-preview-cleanup.yml b/.github/workflows/pr-preview-cleanup.yml new file mode 100644 index 0000000000..239dc2f381 --- /dev/null +++ b/.github/workflows/pr-preview-cleanup.yml @@ -0,0 +1,30 @@ +name: pr-preview-cleanup + +# Removes the GitHub Pages e2e-report preview that `build.yml`'s `deploy-report` +# job published for a PR, once that PR is closed/merged. Kept separate from +# `build.yml` so closing a PR doesn't re-run the whole build + e2e suite. +on: + pull_request: + types: [closed] + +permissions: + contents: write + pull-requests: write + +jobs: + remove-preview: + name: "Remove E2E Report Preview" + runs-on: ubuntu-latest + # Fork PRs never got a preview (read-only token), so nothing to remove. + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Remove PR preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1 + with: + preview-branch: gh-pages + umbrella-dir: pr-preview + action: remove diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 511fc6d2b2..b39deb9957 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -13,62 +13,21 @@ on: required: true type: string -env: - NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN }} - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_SERVER }} +permissions: {} jobs: publish: name: Publish runs-on: ubuntu-latest - permissions: - contents: write - id-token: write # needed for provenance data generation - attestations: write - timeout-minutes: 10 + timeout-minutes: 5 steps: - - uses: actions/checkout@v4 - with: - ref: ${{ inputs.version }} - fetch-depth: 100 - - name: Display version being published + - name: Publish disabled + # NX has been removed (it was driving `nx release publish`) and the + # replacement release tool is not yet in place. This workflow is + # intentionally a no-op so a stray tag push doesn't silently succeed + # without actually publishing anything. Once a release tool is wired + # up, restore the publish steps and remove this guard. run: | - echo "Publishing version: ${{ inputs.version }}" - - - run: jq '.packageManager' package.json | tr -d '"pnpm@' - id: package-manager-version - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: ${{ steps.package-manager-version.outputs.stdout }} - - - uses: nrwl/nx-set-shas@v3 - - - uses: actions/setup-node@v4 - with: - cache: "pnpm" - cache-dependency-path: "**/pnpm-lock.yaml" - node-version-file: ".nvmrc" - - - name: Cache NX - uses: actions/cache@v4 - with: - path: .nx/cache - key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} - restore-keys: | - nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}- - nx-${{ env.NX_BRANCH }}- - nx- - - - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - - - name: Install Dependencies & Build - run: pnpm install && pnpm build - - - name: Print Environment Info - run: pnpm exec nx report - - - name: Publish packages - # Ensure npm 11.5.1 or later for trusted publishing - run: npm install -g npm@latest && pnpm exec nx release publish --access public + echo "::error::Publish workflow is disabled — release tooling is being replaced." + echo "::error::Re-enable this workflow once a release tool is wired up." + exit 1 diff --git a/.github/workflows/relative-ci.yaml b/.github/workflows/relative-ci.yaml index c2b848e202..fdee2a57f5 100644 --- a/.github/workflows/relative-ci.yaml +++ b/.github/workflows/relative-ci.yaml @@ -1,17 +1,24 @@ name: RelativeCI on: + # zizmor: ignore[dangerous-triggers] -- workflow_run is the recommended pattern + # for RelativeCI; this workflow only downloads artifacts and reports bundle stats. workflow_run: workflows: ["build"] types: - completed +permissions: + actions: read + contents: read + jobs: build: runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' steps: - name: Send bundle stats and build information to RelativeCI (editor) - uses: relative-ci/agent-action@v2 + uses: relative-ci/agent-action@fcf45416581928e8dd62eded78ce98c78e5149f8 # v3.2.3 with: artifactName: relative-ci-artifacts-editor key: ${{ secrets.RELATIVE_CI_KEY }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000..0b9ea34841 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,26 @@ +name: GitHub Actions Security Analysis with zizmor + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 diff --git a/.gitignore b/.gitignore index 92f64ba492..a22a821267 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ examples/*/types tsconfig.tsbuildinfo # testing coverage +# Vitest's attachment store (failure screenshots / diffs). Accumulates across +# runs; the HTML reporter copies what it needs into playwright-report/data, +# so the originals are unreferenced cruft. +.vitest-attachments/ # production /build @@ -29,10 +33,18 @@ yarn-error.log* .vercel test-results/ playwright-report/ +blob-report/ release /test-results/ /playwright-report/ +/blob-report/ /playwright/.cache/ .env *.pem .nx/ +# Nightshift plan artifacts (keep out of version control) +.nightshift-plan +.claude/* +!.claude/skills +.playwright-cli +.claude/skills/playwright-cli/screenshots \ No newline at end of file diff --git a/.node-version b/.node-version new file mode 100644 index 0000000000..5bf4400f22 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.15.0 diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index c5ddcef4e7..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v22.14.0 \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 0f4e5b7d67..0000000000 --- a/.prettierrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/prettierrc", - "semi": true, - "singleQuote": false, - "tabWidth": 2, - "printWidth": 80, - "trailingComma": "all", - "bracketSpacing": true, - "arrowParens": "always", - "endOfLine": "lf", - "plugins": ["prettier-plugin-tailwindcss"] -} diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit new file mode 100755 index 0000000000..85fb65b4fc --- /dev/null +++ b/.vite-hooks/pre-commit @@ -0,0 +1 @@ +vp staged diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c224675018..1b6c379e0a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,5 @@ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format - "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] + "recommendations": ["VoidZero.vite-plus-extension-pack"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c24081186..80f6014497 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,16 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.defaultFormatter": "oxc.oxc-vscode", "editor.formatOnSave": true, - "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.formatOnSaveMode": "file", + "editor.codeActionsOnSave": { + "source.fixAll.oxc": "explicit" }, + "oxc.fmt.configPath": "./vite.config.ts", + "npm.scriptRunner": "vp", + "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[javascriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "search.exclude": { "packages/editor/public/types": true, "packages/website/docs/.vitepress": false, @@ -15,11 +21,5 @@ "editor.defaultFormatter": "redhat.vscode-xml" }, "scm.defaultViewMode": "tree", - "search.defaultViewMode": "tree", - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[mdx]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - } + "search.defaultViewMode": "tree" } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..85d7918fe5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +# Project Description + +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. + +# Issue Context + +When prompted to write a new feature, fix a bug, or make some other modification to the code, the project repository on GitHub should be scanned for issues and PRs which are relevant to the task at hand. Before writing any code, a summary of these should be given. If nothing relevant is found, the task can be started immediately. Otherwise, the user should be prompted on next steps. + +This should only be done for new conversations. If GitHub was already scanned in the same conversation, it does not need to be scanned again. + +Once the task is done and the feature is completed, bug is fixed, etc, the user should be reminded of the relevant issues and PRs found in the initial investigation. + +The GitHub CLI should be used to browse issues and PRs. + +# Common Commands + +All commands below are listed under `package.json` in the project root. See `vite.config.ts` for relevant configuration settings. + +- `vp install`: Installs dependencies. +- `vp run dev`: Starts the dev server on port 5173. +- `vp run check`: Checks for linting and formatting issues across the project and attempt resolve issues automatically. +- `vp run build`: Builds the project. +- `vp run preview`: Previews the build on port 3000. +- `vp run test`: Runs unit tests. Append with `-u` to update snapshots. Append with a file name to target only that file. +- `vp run e2e`: Runs end-to-end tests. Append with a file name to target only that file. +- `vp run e2e:updateSnaps`: Runs end-to-end tests & updates snapshots. Append with a file name to target only that file. +- `vp help`: Prints a list of all availabel commands. + +# Common Entry Points + +When writing a new feature, bug fix, or other modification, it may not be immediately clear where the code for it should be. There are a few files which are good to start looking in when this is the case: + +- `/packages/core/src/editor/BlockNoteEditor.ts`: Contains the class for the core BlockNote editor. Every editor command & event can be traced from here. +- `/packages/react/src/editor/BlockNoteView.tsx`: Contains the `BlockNoteViewEditor` component, which is the base for rendering the editor and its UI elements. Whenever the UI functionality (and often styling) needs to be changed, it will be a descendant of `BlockNoteViewEditor`. +- `/packages/mantine/src/BlockNoteView.tsx`: Contains the Mantine version of `BlockNoteView`. This can be thought of as a skin for `BlockNoteViewEditor` that uses the Mantine component library. Therefore, changes in `BlockNoteViewEditor` may also have to be propagted to it. + - The same applies for `BlockNoteView.tsx` in `/packages/ariakit` and `/packages/shadcn`, though Mantine is the defacto default version of `BlockNoteView`. + +# Additional Notes + +- Do not create git commits. diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c3bcc15e..87fed6062a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,266 @@ +## 0.51.4 (2026-06-02) + +### 🩹 Fixes + +- add explicit type annotations for callback params losing contextual typing ([#2815](https://github.com/TypeCellOS/BlockNote/pull/2815)) +- Comments emoji picker button issues (BLO-1199) ([#2769](https://github.com/TypeCellOS/BlockNote/pull/2769)) +- **core:** add editor cleanup in BlockNoteEditor.test.ts to prevent unhandled DOMObserver errors ([#2816](https://github.com/TypeCellOS/BlockNote/pull/2816)) +- **table:** prevent crash when pressing Enter in a table cell ([#2793](https://github.com/TypeCellOS/BlockNote/pull/2793)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick @nperez0111 +- Yuki Terashima @y-temp4 + +## 0.51.3 (2026-05-26) + +### 🩹 Fixes + +- table cell colors (BLO-1198) ([#2770](https://github.com/TypeCellOS/BlockNote/pull/2770)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski + +## 0.51.2 (2026-05-20) + +### 🩹 Fixes + +- color picker icons (BLO-1189) ([#2762](https://github.com/TypeCellOS/BlockNote/pull/2762)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski + +## 0.51.1 (2026-05-18) + +### 🩹 Fixes + +- backslash newlines when copying from a code block ([#2709](https://github.com/TypeCellOS/BlockNote/pull/2709)) + +### ❤️ Thank You + +- Claude Opus 4.7 (1M context) +- Nick Perez + +## 0.51.0 (2026-05-14) + +### 🚀 Features + +- Trailing block extension rewrite ([#2733](https://github.com/TypeCellOS/BlockNote/pull/2733)) +- **markdown:** replace unified.js with custom markdown parser/serializer ([#2624](https://github.com/TypeCellOS/BlockNote/pull/2624)) +- **react:** configurable portal targets for floating UI ([#2729](https://github.com/TypeCellOS/BlockNote/pull/2729), [#2692](https://github.com/TypeCellOS/BlockNote/issues/2692)) + +### 🩹 Fixes + +- Pasting plain text from VSCode (BLO-366) ([#2713](https://github.com/TypeCellOS/BlockNote/pull/2713)) +- Parse new lines in `text/plain` as line breaks (BLO-1170) ([#2712](https://github.com/TypeCellOS/BlockNote/pull/2712)) +- Code block PDF export (BLO-987) ([#2725](https://github.com/TypeCellOS/BlockNote/pull/2725)) +- Formatting toolbar opening when inserting file block with `trailingBlock: false` (BLO-860) ([#2704](https://github.com/TypeCellOS/BlockNote/pull/2704)) +- numbered list item decorations missed on initial render ([#2734](https://github.com/TypeCellOS/BlockNote/pull/2734)) +- flicker-free mobile formatting toolbar via CSS custom properties ([#2617](https://github.com/TypeCellOS/BlockNote/pull/2617), [#2616](https://github.com/TypeCellOS/BlockNote/issues/2616)) +- add `bn-thread-orphaned` CSS class to distinguish orphaned threads ([#2737](https://github.com/TypeCellOS/BlockNote/pull/2737), [#2735](https://github.com/TypeCellOS/BlockNote/issues/2735)) +- set width attribute on image and video elements in editor render ([#2740](https://github.com/TypeCellOS/BlockNote/pull/2740), [#2726](https://github.com/TypeCellOS/BlockNote/issues/2726)) +- **a11y:** use figure/figcaption for media block captions ([#2717](https://github.com/TypeCellOS/BlockNote/pull/2717)) +- **ai:** loosen serialization of blocks in columns ([#2716](https://github.com/TypeCellOS/BlockNote/pull/2716), [#2718](https://github.com/TypeCellOS/BlockNote/pull/2718)) +- **core:** trigger codeblock input rule on Enter and place cursor inside ([#2686](https://github.com/TypeCellOS/BlockNote/pull/2686)) +- **core:** preserve list item type when pasting into empty list items ([#2722](https://github.com/TypeCellOS/BlockNote/pull/2722), [#2330](https://github.com/TypeCellOS/BlockNote/issues/2330)) +- **core:** unmount editors in transformPasted tests to prevent unhandled error ([e62880b21](https://github.com/TypeCellOS/BlockNote/commit/e62880b21)) +- **drag-n-drop:** support PDF block drag & drop (BLO-893) ([#2714](https://github.com/TypeCellOS/BlockNote/pull/2714)) +- **i18:** improve french translation for empty toggle list ([#2721](https://github.com/TypeCellOS/BlockNote/pull/2721)) +- **markdown:** emit tight lists when serializing blocks to markdown ([#2715](https://github.com/TypeCellOS/BlockNote/pull/2715)) +- **markdown:** skip placeholder text for empty files ([#434](https://github.com/TypeCellOS/BlockNote/pull/434), [#2719](https://github.com/TypeCellOS/BlockNote/pull/2719)) +- **markdown:** stable round-trip for tables, captions, and audio ([#2720](https://github.com/TypeCellOS/BlockNote/pull/2720)) +- **tests:** stabilize webkit keyboard handler tests with programmatic cursor positioning ([#2746](https://github.com/TypeCellOS/BlockNote/pull/2746)) + +### ❤️ Thank You + +- Cyril G +- Manuel Raynaud @lunika +- Matthew Lipski @matthewlipski +- Movm +- Nick Perez +- Nick the Sick @nperez0111 + +## 0.50.0 (2026-05-04) + +### 🚀 Features + +- Dark mode styling for file block wrapper component (BLO-866) ([#2680](https://github.com/TypeCellOS/BlockNote/pull/2680)) +- Drag hendle menu delete button removes all other blocks in selection (BLO-1007) ([#2683](https://github.com/TypeCellOS/BlockNote/pull/2683)) +- Enter moves selection to cell below in tables (BLO-1006) ([#2685](https://github.com/TypeCellOS/BlockNote/pull/2685)) +- additional heading top padding (BLO-1008) ([#2690](https://github.com/TypeCellOS/BlockNote/pull/2690)) +- Code mark input rule edge case (BLO-938) ([#2698](https://github.com/TypeCellOS/BlockNote/pull/2698)) +- **mantine:** upgrade @mantine/core and @mantine/hooks to v9.0.2 ([#2655](https://github.com/TypeCellOS/BlockNote/pull/2655)) + +### 🩹 Fixes + +- Hardcoded strings in comment components (BLO-1033) ([#2681](https://github.com/TypeCellOS/BlockNote/pull/2681)) +- Color naming & CSS (BLO-946) ([#2684](https://github.com/TypeCellOS/BlockNote/pull/2684)) +- link HTML attributes (BLO-915) ([#2687](https://github.com/TypeCellOS/BlockNote/pull/2687)) +- guard hideMenuIfNotFrozen against undefined view state ([#2694](https://github.com/TypeCellOS/BlockNote/pull/2694), [#2699](https://github.com/TypeCellOS/BlockNote/pull/2699)) +- Clicking comment overlapping link opens link (BLO-1091) ([#2696](https://github.com/TypeCellOS/BlockNote/pull/2696)) +- prevent table row drag from moving an extra adjacent row ([#2703](https://github.com/TypeCellOS/BlockNote/pull/2703)) +- **clipboard:** use ProseMirror selection state for Shadow DOM compatibility ([#2677](https://github.com/TypeCellOS/BlockNote/pull/2677)) + +### ❤️ Thank You + +- jt_fox @LimChaeJune +- Matthew Lipski @matthewlipski +- Nick Perez +- Wieland Lindenthal +- Yousef + +## 0.49.0 (2026-04-24) + +### 🚀 Features + +- simplify links by inlining it to BlockNote ([#2623](https://github.com/TypeCellOS/BlockNote/pull/2623)) +- add Unicode quotation mark input rule for quote blocks ([#2673](https://github.com/TypeCellOS/BlockNote/pull/2673)) + +### 🩹 Fixes + +- Inserting link removes comment & add comment button click buggy ([#2620](https://github.com/TypeCellOS/BlockNote/pull/2620), [#2573](https://github.com/TypeCellOS/BlockNote/issues/2573)) +- `useEditorDOMElement` hook ([#2619](https://github.com/TypeCellOS/BlockNote/pull/2619)) +- text color was not applying to table block ([#2663](https://github.com/TypeCellOS/BlockNote/pull/2663)) +- Drag preview blocking drops when overlapping the editor (BLO-996) ([#2670](https://github.com/TypeCellOS/BlockNote/pull/2670)) +- Drag & drop of blocks without inline content opens formatting toolbar (BLO-1116) ([#2628](https://github.com/TypeCellOS/BlockNote/pull/2628), [#2603](https://github.com/TypeCellOS/BlockNote/issues/2603)) +- save file caption/name on every keystroke instead of on close ([#2575](https://github.com/TypeCellOS/BlockNote/pull/2575)) +- prevent FloatingFocusManager from resetting editor selection ([#2525](https://github.com/TypeCellOS/BlockNote/pull/2525), [#2664](https://github.com/TypeCellOS/BlockNote/pull/2664)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- miadnguyen @miadnguyen +- mianguyen +- Nick Perez +- Yousef + +## 0.48.1 (2026-04-16) + +### 🩹 Fixes + +- make CustomChange compatible with prosemirror-changeset 2.4.1 ([#2647](https://github.com/TypeCellOS/BlockNote/pull/2647)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability (CVE-2025-62718) ([c1ef3018a](https://github.com/TypeCellOS/BlockNote/commit/c1ef3018a)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability ([#2653](https://github.com/TypeCellOS/BlockNote/pull/2653)) +- **docx-exporter:** omit w:lang when no locale provided instead of defaulting to en-US ([#2651](https://github.com/TypeCellOS/BlockNote/pull/2651)) + +### ❤️ Thank You + +- Claude Opus 4.6 (1M context) +- Nick Perez +- Nick the Sick +- Stephan Meijer @StephanMeijer + +## 0.48.0 (2026-04-13) + +### 🚀 Features + +- upgrade shiki to v4 and prosemirror-highlight to v0.15.1 ([#2625](https://github.com/TypeCellOS/BlockNote/pull/2625)) +- upgrade nx to 22.6.4 and liveblocks to 3.17.0 ([#2627](https://github.com/TypeCellOS/BlockNote/pull/2627)) + +### 🩹 Fixes + +- Image block selection clears on mouse leave in Safari ([#2613](https://github.com/TypeCellOS/BlockNote/pull/2613)) +- Backspace bug when current block is empty and previous block's last child is empty ([#2610](https://github.com/TypeCellOS/BlockNote/pull/2610)) +- allow using latest @tiptap/extension-link version ([1ae8de713](https://github.com/TypeCellOS/BlockNote/commit/1ae8de713)) +- restore depth guard in getParentBlockInfo to prevent RangeError (blo-1103) ([#2585](https://github.com/TypeCellOS/BlockNote/pull/2585)) +- pin better-auth to ~1.4.x to fix docs build ([bda30458a](https://github.com/TypeCellOS/BlockNote/commit/bda30458a)) +- hide side menu on scroll instead of overflow hacks ([#2630](https://github.com/TypeCellOS/BlockNote/pull/2630), [#2043](https://github.com/TypeCellOS/BlockNote/issues/2043)) +- disable default UI when no components context is found ([#2611](https://github.com/TypeCellOS/BlockNote/pull/2611)) +- add .js extension to fast-deep-equal ESM import ([#2641](https://github.com/TypeCellOS/BlockNote/pull/2641)) +- placeholder when overflowing now wraps ([#2291](https://github.com/TypeCellOS/BlockNote/pull/2291)) +- **core:** fix unnesting blocks with siblings (BLO-1017) ([#2601](https://github.com/TypeCellOS/BlockNote/pull/2601)) +- **core:** backspace mid-text next to columnList moves block BLO-1126 ([#2629](https://github.com/TypeCellOS/BlockNote/pull/2629)) + +### 🔥 Performance + +- optimize plugin traversals for large documents BLO-1111 ([#2600](https://github.com/TypeCellOS/BlockNote/pull/2600)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Claude Opus 4.6 (1M context) +- hedi-ghodhbane @hedi-ghodhbane +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick @nperez0111 +- Yousef + +## 0.47.3 (2026-03-25) + +### 🩹 Fixes + +- **core:** preserve whitespace edge cases but collapse html formatting newlines (BLO-1065) ([#2551](https://github.com/TypeCellOS/BlockNote/pull/2551), [#2230](https://github.com/TypeCellOS/BlockNote/issues/2230)) + +### ❤️ Thank You + +- Yousef + +## 0.47.2 (2026-03-20) + +### 🩹 Fixes + +- use
/ for toggle block HTML export ([#2524](https://github.com/TypeCellOS/BlockNote/pull/2524)) +- remove @hocuspocus/provider peer dependency by inlining tiptap comment types BLO-1064 ([#2564](https://github.com/TypeCellOS/BlockNote/pull/2564)) +- **core:** slash menu fails in custom blocks after space BLO-1036 ([#2553](https://github.com/TypeCellOS/BlockNote/pull/2553)) +- **i18n:** fix typo in russian translation ([#2560](https://github.com/TypeCellOS/BlockNote/pull/2560)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Drone +- Yousef + +## 0.47.1 (2026-03-02) + +### 🩹 Fixes + +- typeerror cannot read properties of undefined ([#2522](https://github.com/TypeCellOS/BlockNote/pull/2522)) +- handle more delete key cases ([#2126](https://github.com/TypeCellOS/BlockNote/pull/2126)) +- add delay for `data-active` in collab cursors ([#2383](https://github.com/TypeCellOS/BlockNote/pull/2383)) +- disable slash menu in table content #2408 ([#2504](https://github.com/TypeCellOS/BlockNote/pull/2504), [#2408](https://github.com/TypeCellOS/BlockNote/issues/2408)) +- **ai:** selections broken due to floating-ui focus manager ([#2527](https://github.com/TypeCellOS/BlockNote/pull/2527)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Yousef + +## 0.47.0 (2026-02-23) + +### 🚀 Features + +- update suggestion menu component ([#2397](https://github.com/TypeCellOS/BlockNote/pull/2397)) +- **i18n:** add Persian (fa) localization support ([#2447](https://github.com/TypeCellOS/BlockNote/pull/2447)) +- **i18n:** add Uzbek (uz) localization support ([#2506](https://github.com/TypeCellOS/BlockNote/pull/2506)) + +### 🩹 Fixes + +- prevent nested bullet list icon rendering as emoji on iOS 18+ ([#2394](https://github.com/TypeCellOS/BlockNote/pull/2394), [#2399](https://github.com/TypeCellOS/BlockNote/pull/2399)) +- ignore drag & drop from unrelated events #1968 ([#2346](https://github.com/TypeCellOS/BlockNote/pull/2346), [#1968](https://github.com/TypeCellOS/BlockNote/issues/1968)) +- disable checkbox when editor is not editable #2406 ([#2448](https://github.com/TypeCellOS/BlockNote/pull/2448), [#2406](https://github.com/TypeCellOS/BlockNote/issues/2406)) +- Backspace/enter behaviour in empty block with children ([#2451](https://github.com/TypeCellOS/BlockNote/pull/2451)) +- handle pasting into table cells better, by collapsing their content to inline #2410 ([#2449](https://github.com/TypeCellOS/BlockNote/pull/2449), [#2410](https://github.com/TypeCellOS/BlockNote/issues/2410)) +- **accessibility:** ai combobox aria-activedescendant ([#2413](https://github.com/TypeCellOS/BlockNote/pull/2413)) +- **ai:** no more scrolling to top when opening AI menu ([#2503](https://github.com/TypeCellOS/BlockNote/pull/2503)) +- **docs:** unicode char not rendered in bug template ([f13e270be](https://github.com/TypeCellOS/BlockNote/commit/f13e270be)) + +### ❤️ Thank You + +- Cyril G @Ovgodd +- Dex Devlon @bxff +- Matthew Lipski @matthewlipski +- MDSAM05 @MDSAM05 +- Mohammad RAHMANI @Mrahmani71 +- Nick Perez +- Ogabek @OgabekYuldoshev +- Wouter Vroege +- Yousef + ## 0.46.2 (2026-01-27) ### 🩹 Fixes diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8fe42686f0..a2e0099d73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,10 +55,10 @@ This diagram illustrates the release workflow for the BlockNote monorepo. Essentially, when the maintainers have decided to release a new version of BlockNote, they will: - 1. Check that the `main` branch is in a releasable state: +1. Check that the `main` branch is in a releasable state: - CI status of main branch is green - Builds are passing - 2. Bump the package versions using the `pnpm run deploy` command. This command will: +2. Bump the package versions using the `pnpm run deploy` command. This command will: 1. Based on semantic versioning, determine the next version number. 2. Apply the new version number to all publishable packages within the monorepo. 3. Generate a changelog for the new version. @@ -80,8 +80,8 @@ The release workflow will: From time to time, you may need to publish a new package to npm. To do this, you cannot just deploy the package to npm, you need to: - 1. Run `nx release version --dry-run` and check that the version number is correct for the package. +1. Run `nx release version --dry-run` and check that the version number is correct for the package. - Once this is done, you can run `nx release version` to actually apply the version bump locally (staged to your local git repo). - 2. Run `nx release changelog --from --dry-run` and check that the changelog is correct for the package. +2. Run `nx release changelog --from --dry-run` and check that the changelog is correct for the package. - Once this is done, you can run the same command without the `--dry-run` flag to actually apply the changelog, commit & push the changes to the `main` branch. - 3. The release workflow will automatically publish the package to npm. +3. The release workflow will automatically publish the package to npm. diff --git a/docs/app/(home)/_components/BlockCatalog.tsx b/docs/app/(home)/_components/BlockCatalog.tsx index 72804ec79e..6d1c86e36e 100644 --- a/docs/app/(home)/_components/BlockCatalog.tsx +++ b/docs/app/(home)/_components/BlockCatalog.tsx @@ -38,9 +38,9 @@ export const BlockCatalog: React.FC = () => {
{/* Subtle decorative elements */}
-
+
-
+
@@ -51,7 +51,7 @@ export const BlockCatalog: React.FC = () => {

Build anything, block by block.

-

+

Every BlockNote document is a collection of blocks—headings, lists, images, and more. Use the built-in blocks, customize them to fit your needs, or create entirely new ones. diff --git a/docs/app/(home)/_components/DigitalCommons.tsx b/docs/app/(home)/_components/DigitalCommons.tsx index c4cc4e941e..6bd11d7b06 100644 --- a/docs/app/(home)/_components/DigitalCommons.tsx +++ b/docs/app/(home)/_components/DigitalCommons.tsx @@ -32,7 +32,7 @@ export const DigitalCommons: React.FC = () => { {/* Eyebrow with EU flag only */}

🇪🇺 - + Digital Commons
@@ -64,7 +64,7 @@ export const DigitalCommons: React.FC = () => {

{/* Compelling social proof - simpler */} -

+

"Building Digital Commons means better tools, data sovereignty, and shared progress."

diff --git a/docs/app/(home)/_components/FAQ.tsx b/docs/app/(home)/_components/FAQ.tsx index 158c5c691a..5fbe0bda40 100644 --- a/docs/app/(home)/_components/FAQ.tsx +++ b/docs/app/(home)/_components/FAQ.tsx @@ -36,7 +36,7 @@ export const FAQ: React.FC = () => {
{faqs.map((faq, index) => (
-

+

{faq.question}

diff --git a/docs/app/(home)/_components/FeatureSection.tsx b/docs/app/(home)/_components/FeatureSection.tsx index ef2c62eea1..c7a9973005 100644 --- a/docs/app/(home)/_components/FeatureSection.tsx +++ b/docs/app/(home)/_components/FeatureSection.tsx @@ -92,7 +92,7 @@ export const FeatureSection: React.FC = ({ {/* Right Visual */}

{/*
*/} -
+
{children}
diff --git a/docs/app/(home)/_components/FrameworkPill.tsx b/docs/app/(home)/_components/FrameworkPill.tsx index eef63da2ba..82e6b90216 100644 --- a/docs/app/(home)/_components/FrameworkPill.tsx +++ b/docs/app/(home)/_components/FrameworkPill.tsx @@ -5,7 +5,7 @@ export const FrameworkPill: React.FC<{ color: string; icon?: React.ReactNode; }> = ({ name, color, icon }) => ( -
+
{icon ||
} {name}
diff --git a/docs/app/(home)/_components/Hero.tsx b/docs/app/(home)/_components/Hero.tsx index 4e383ce2f3..2bb0dc5b52 100644 --- a/docs/app/(home)/_components/Hero.tsx +++ b/docs/app/(home)/_components/Hero.tsx @@ -21,7 +21,7 @@ export const Hero: React.FC = () => { {BADGES.map((badge, index) => (
{badge.icon} @@ -31,11 +31,11 @@ export const Hero: React.FC = () => { ))} -

+

Build a Notion-style{" "} editor in minutes.

-

+

The AI-native, open source rich text editor for React. Add a{" "} fully customizable modern block-based editing @@ -60,7 +60,7 @@ export const Hero: React.FC = () => {

-
+
diff --git a/docs/app/(home)/_components/Letter.tsx b/docs/app/(home)/_components/Letter.tsx index 8dbbba7091..dc5cff7b3b 100644 --- a/docs/app/(home)/_components/Letter.tsx +++ b/docs/app/(home)/_components/Letter.tsx @@ -15,7 +15,7 @@ export const Letter: React.FC = () => {
-

+

Let's build.

@@ -23,7 +23,7 @@ export const Letter: React.FC = () => {
-

+

Building a rich text editor is one of the hardest engineering challenges on the web. It used to take months of specialized work. @@ -91,10 +91,10 @@ export const Letter: React.FC = () => {

- + The Team - + BlockNote Creators
diff --git a/docs/app/(home)/_components/Marquee.tsx b/docs/app/(home)/_components/Marquee.tsx index a5b4e3f190..ec2afe390b 100644 --- a/docs/app/(home)/_components/Marquee.tsx +++ b/docs/app/(home)/_components/Marquee.tsx @@ -3,7 +3,7 @@ import { FrameworkPill } from "./FrameworkPill"; export const Marquee: React.FC = () => (
-
+
@@ -20,7 +20,7 @@ export const Marquee: React.FC = () => (
-
+
diff --git a/docs/app/(home)/_components/Pricing.tsx b/docs/app/(home)/_components/Pricing.tsx index df3a553bfe..7f175abebb 100644 --- a/docs/app/(home)/_components/Pricing.tsx +++ b/docs/app/(home)/_components/Pricing.tsx @@ -8,7 +8,7 @@ export const Pricing: React.FC = () => {
{/* Header */}
-

+

Transparent pricing

diff --git a/docs/app/(home)/_components/SpotlightCard.tsx b/docs/app/(home)/_components/SpotlightCard.tsx index 8ee0b46adf..5e7f3a8ac1 100644 --- a/docs/app/(home)/_components/SpotlightCard.tsx +++ b/docs/app/(home)/_components/SpotlightCard.tsx @@ -10,7 +10,9 @@ export const SpotlightCard: React.FC<{ const [opacity, setOpacity] = useState(0); const handleMouseMove = (e: React.MouseEvent) => { - if (!divRef.current) return; + if (!divRef.current) { + return; + } const rect = divRef.current.getBoundingClientRect(); setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }); setOpacity(1); diff --git a/docs/app/(home)/_components/TextLoop.tsx b/docs/app/(home)/_components/TextLoop.tsx index 5947d0772a..797209004b 100644 --- a/docs/app/(home)/_components/TextLoop.tsx +++ b/docs/app/(home)/_components/TextLoop.tsx @@ -36,7 +36,9 @@ export function TextLoop({ const items = Children.toArray(children); useEffect(() => { - if (!trigger) return; + if (!trigger) { + return; + } const intervalMs = interval * 1000; const timer = setInterval(() => { diff --git a/docs/app/(home)/_components/USP.tsx b/docs/app/(home)/_components/USP.tsx index 69c7469d54..7c0060b15b 100644 --- a/docs/app/(home)/_components/USP.tsx +++ b/docs/app/(home)/_components/USP.tsx @@ -66,7 +66,7 @@ export const USP = () => { > Contact Us -
+
For Enterprise
diff --git a/docs/app/[...slug]/page.tsx b/docs/app/[...slug]/page.tsx index 2c95426668..8d10789a3c 100644 --- a/docs/app/[...slug]/page.tsx +++ b/docs/app/[...slug]/page.tsx @@ -9,7 +9,9 @@ import { notFound } from "next/navigation"; export default async function Page(props: PageProps<"/[...slug]">) { const params = await props.params; const page = source.getPage(params.slug); - if (!page) notFound(); + if (!page) { + notFound(); + } const MDX = page.data.body; @@ -36,7 +38,9 @@ export async function generateMetadata( ): Promise { const params = await props.params; const page = source.getPage(params.slug); - if (!page) notFound(); + if (!page) { + notFound(); + } return getFullMetadata({ title: page.data.title, diff --git a/docs/app/demo/_components/DemoEditor.tsx b/docs/app/demo/_components/DemoEditor.tsx index 08b290621a..fa16d07a8f 100644 --- a/docs/app/demo/_components/DemoEditor.tsx +++ b/docs/app/demo/_components/DemoEditor.tsx @@ -119,7 +119,7 @@ export function DemoEditor() { : ""; const content = !roomId ? ( -
+
{}} user={HARDCODED_USERS[0]} @@ -138,19 +138,19 @@ export function DemoEditor() { return (
-
+
⚡️ Collaborate live! Share this URL: -
+
e.currentTarget.select()} /> -
+
-
- - - -
+ {exportOpen && ( +
+ + + +
+ )}
-
+
- ); } diff --git a/docs/app/docs/[[...slug]]/page.tsx b/docs/app/docs/[[...slug]]/page.tsx index 91e63187cc..0494368f6b 100644 --- a/docs/app/docs/[[...slug]]/page.tsx +++ b/docs/app/docs/[[...slug]]/page.tsx @@ -10,7 +10,9 @@ import { notFound } from "next/navigation"; export default async function Page(props: PageProps<"/docs/[[...slug]]">) { const params = await props.params; const page = source.getPage(params.slug); - if (!page) notFound(); + if (!page) { + notFound(); + } const MDX = page.data.body; @@ -54,7 +56,9 @@ export async function generateMetadata( ): Promise { const params = await props.params; const page = source.getPage(params.slug); - if (!page) notFound(); + if (!page) { + notFound(); + } return getFullMetadata({ title: page.data.title, diff --git a/docs/app/examples/[[...slug]]/page.tsx b/docs/app/examples/[[...slug]]/page.tsx index 1698b3533f..4f3a95b7cf 100644 --- a/docs/app/examples/[[...slug]]/page.tsx +++ b/docs/app/examples/[[...slug]]/page.tsx @@ -55,7 +55,7 @@ export default async function Page(props: PageProps<"/examples/[[...slug]]">) {
Try the Demo → @@ -87,7 +87,9 @@ export async function generateMetadata( ): Promise { const params = await props.params; const page = source.getPage(params.slug); - if (!page) notFound(); + if (!page) { + notFound(); + } return getFullMetadata({ title: page.data.title, diff --git a/docs/app/llms.txt/route.ts b/docs/app/llms.txt/route.ts index 24fc409d0b..c1dbcd00c1 100644 --- a/docs/app/llms.txt/route.ts +++ b/docs/app/llms.txt/route.ts @@ -19,7 +19,9 @@ Important Notes: for (const page of source.getPages()) { const dir = page.slugs[0]; const list = map.get(dir) ?? []; - list.push(`- [${page.data.title}](${page.url}): ${page.data.description}`); + list.push( + `- [${page.data.title}](${page.url}.md): ${page.data.description}`, + ); map.set(dir, list); } diff --git a/docs/app/og/docs/[...slug]/route.tsx b/docs/app/og/docs/[...slug]/route.tsx index 9d62877c08..6c498f6485 100644 --- a/docs/app/og/docs/[...slug]/route.tsx +++ b/docs/app/og/docs/[...slug]/route.tsx @@ -10,7 +10,9 @@ export async function GET( ) { const { slug } = await params; const page = source.getPage(slug.slice(0, -1)); - if (!page || slug[slug.length - 1] !== "image.png") notFound(); + if (!page || slug[slug.length - 1] !== "image.png") { + notFound(); + } let title = page.data.imageTitle || page.data.title; diff --git a/docs/app/og/examples/[...slug]/route.tsx b/docs/app/og/examples/[...slug]/route.tsx index 31d79e786b..8e8b98bb92 100644 --- a/docs/app/og/examples/[...slug]/route.tsx +++ b/docs/app/og/examples/[...slug]/route.tsx @@ -10,7 +10,9 @@ export async function GET( ) { const { slug } = await params; const page = source.getPage(slug.slice(0, -1)); - if (!page || slug[slug.length - 1] !== "image.png") notFound(); + if (!page || slug[slug.length - 1] !== "image.png") { + notFound(); + } let title = page.data.imageTitle || page.data.title; diff --git a/docs/app/og/pages/[...slug]/route.tsx b/docs/app/og/pages/[...slug]/route.tsx index 7822756e46..a55bc3e602 100644 --- a/docs/app/og/pages/[...slug]/route.tsx +++ b/docs/app/og/pages/[...slug]/route.tsx @@ -10,7 +10,9 @@ export async function GET( ) { const { slug } = await params; const page = source.getPage(slug.slice(0, -1)); - if (!page || slug[slug.length - 1] !== "image.png") notFound(); + if (!page || slug[slug.length - 1] !== "image.png") { + notFound(); + } let title = page.data.imageTitle || page.data.title; diff --git a/docs/app/pricing/PricingTiers.tsx b/docs/app/pricing/PricingTiers.tsx new file mode 100644 index 0000000000..0c7c1c9d48 --- /dev/null +++ b/docs/app/pricing/PricingTiers.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { cn } from "@/lib/fumadocs/cn"; +import { useState } from "react"; +import { Tier, Tiers } from "./tiers"; + +type Frequency = "month" | "year"; + +export function PricingTiers({ tiers }: { tiers: Tier[] }) { + const [frequency, setFrequency] = useState("year"); + + return ( + <> + {/* Frequency Toggle */} +
+ + Monthly + + + + Yearly + + + Save 50% + +
+ + + + ); +} diff --git a/docs/app/pricing/page.tsx b/docs/app/pricing/page.tsx index e7c1368186..16ab36e73d 100644 --- a/docs/app/pricing/page.tsx +++ b/docs/app/pricing/page.tsx @@ -1,5 +1,5 @@ import { FAQ } from "@/app/pricing/faq"; -import { Tier, Tiers } from "@/app/pricing/tiers"; +import { Tier } from "@/app/pricing/tiers"; import { InfiniteSlider } from "@/components/InfiniteSlider"; import { Tooltip, @@ -9,6 +9,7 @@ import { } from "@/components/ui/tooltip"; import { getFullMetadata } from "@/lib/getFullMetadata"; import Link from "next/link"; +import { PricingTiers } from "./PricingTiers"; export const metadata = getFullMetadata({ title: "Pricing", @@ -80,7 +81,7 @@ const tiers: Tier[] = [ badge: "Recommended", description: "Commercial license for access to advanced features and technical support.", - price: { month: 390, year: 48 }, + price: { month: 390, year: 2340 }, features: [ Commercial license for XL packages: @@ -144,7 +145,7 @@ export default function Pricing() {
{/* Header */}
-

+

Pricing

@@ -162,8 +163,8 @@ export default function Pricing() {

- {/* Pricing Tiers */} - + {/* Pricing Tiers with Toggle */} + {/* Social proof */}
@@ -207,10 +208,6 @@ export default function Pricing() { {/* FAQ */}
-
); } diff --git a/docs/app/pricing/tiers.tsx b/docs/app/pricing/tiers.tsx index 70702d76ec..2014f57b62 100644 --- a/docs/app/pricing/tiers.tsx +++ b/docs/app/pricing/tiers.tsx @@ -23,7 +23,19 @@ export type Tier = { cta?: "get-started" | "buy" | "contact"; }; -function TierCTAButton({ tier }: { tier: Tier }) { +const BUSINESS_PLAN_TYPES = new Set(["business", "business-yearly"]); + +function isBusinessPlan(planType: string) { + return BUSINESS_PLAN_TYPES.has(planType); +} + +function TierCTAButton({ + tier, + frequency, +}: { + tier: Tier; + frequency: Frequency; +}) { const { data: session } = useSession(); let text = tier.cta === "get-started" @@ -38,10 +50,11 @@ function TierCTAButton({ tier }: { tier: Tier }) { if (session.planType === "free") { text = "Buy now"; } else { - text = - session.planType === tier.id - ? "Manage subscription" - : "Update subscription"; + const isCurrentPlan = + tier.id === "business" + ? isBusinessPlan(session.planType ?? "") + : session.planType === tier.id; + text = isCurrentPlan ? "Manage subscription" : "Update subscription"; } } @@ -68,9 +81,6 @@ function TierCTAButton({ tier }: { tier: Tier }) { } track("Signup", { tier: tier.id }); - // ... rest of analytic logic kept simple for brevity in replacement, - // in real implementation we keep the existing logic. - // Re-injecting existing analytics logic below to ensure no regression. if (!session) { Sentry.captureEvent({ message: "click-pricing-signup", @@ -90,9 +100,17 @@ function TierCTAButton({ tier }: { tier: Tier }) { track("click-pricing-buy-now", { tier: tier.id }); e.preventDefault(); e.stopPropagation(); - await authClient.checkout({ slug: tier.id }); + const checkoutSlug = + frequency === "year" && tier.id === "business" + ? "business-yearly" + : tier.id; + await authClient.checkout({ slug: checkoutSlug }); } else { - if (session.planType === tier.id) { + const isCurrentPlan = + tier.id === "business" + ? isBusinessPlan(session.planType ?? "") + : session.planType === tier.id; + if (isCurrentPlan) { Sentry.captureEvent({ message: "click-pricing-manage-subscription", level: "info", @@ -183,7 +201,7 @@ export function Tiers({ {/* Popular badge */} {tier.mostPopular && (
- + {tier.badge ?? "Most Popular"}
@@ -196,7 +214,7 @@ export function Tiers({ {tier.title}

{tier.tagline && ( -

+

{tier.tagline}

)} @@ -208,6 +226,28 @@ export function Tiers({ {tier.price} + ) : frequency === "year" ? ( +
+
+ + ${Math.round(tier.price.year / 12)} + + + /month + +
+
+ + ${tier.price.month}/mo + + + now -50% + +
+

+ ${tier.price.year.toLocaleString()} billed yearly +

+
) : (
@@ -227,7 +267,7 @@ export function Tiers({ {/* CTA */}
- +
{/* Features */} diff --git a/docs/app/styles.css b/docs/app/styles.css index 7aa8d6bbc7..c1f4c85362 100644 --- a/docs/app/styles.css +++ b/docs/app/styles.css @@ -53,14 +53,6 @@ body { box-shadow: unset !important; } -.demo { - overflow: none; -} - -.demo .bn-container { - position: relative; -} - .demo .bn-container:not(.bn-comment-editor), .demo .bn-editor { height: 100%; diff --git a/docs/components/AuthNavButton.tsx b/docs/components/AuthNavButton.tsx index 0234a944f7..152fc44807 100644 --- a/docs/components/AuthNavButton.tsx +++ b/docs/components/AuthNavButton.tsx @@ -139,8 +139,8 @@ function NavbarMenu({ */} - - + + {menuItems} diff --git a/docs/components/AuthenticationPage.tsx b/docs/components/AuthenticationPage.tsx index d68c0cf50e..d29d380d9e 100644 --- a/docs/components/AuthenticationPage.tsx +++ b/docs/components/AuthenticationPage.tsx @@ -34,7 +34,7 @@ function AuthenticationInput(props: { type={props.type} required autoComplete={props.type} - className="text-fd-accent-foreground outline-fd-border focus:outline-fd-primary focus:outline-3 block w-full rounded-md bg-white px-3 py-1.5 text-base outline-2 -outline-offset-1 focus:-outline-offset-2 sm:text-sm/6 dark:bg-black" + className="text-fd-accent-foreground outline-fd-border focus:outline-fd-primary block w-full rounded-md bg-white px-3 py-1.5 text-base outline-2 -outline-offset-1 focus:outline-3 focus:-outline-offset-2 sm:text-sm/6 dark:bg-black" onChange={props.onChange} />
@@ -431,7 +431,7 @@ export default function AuthenticationPage(props: {
-
+
diff --git a/docs/components/CustomDocsLayout.tsx b/docs/components/CustomDocsLayout.tsx index 61b93cc79c..9b94102f35 100644 --- a/docs/components/CustomDocsLayout.tsx +++ b/docs/components/CustomDocsLayout.tsx @@ -38,7 +38,7 @@ export function CustomDocsLayout({ ...base.nav, children: ( <> - {/* + {/* Added custom sidebar toggle button for mobile views. This is specific to Docs, because it triggers the left sidebar (not the top nav) */} diff --git a/docs/components/ProBadge.tsx b/docs/components/ProBadge.tsx index 72cfc78dcc..e4fc31df57 100644 --- a/docs/components/ProBadge.tsx +++ b/docs/components/ProBadge.tsx @@ -1,6 +1,6 @@ export function ProBadge() { return ( - + Pro ); diff --git a/docs/components/fumadocs/layout/home/client.tsx b/docs/components/fumadocs/layout/home/client.tsx index 0633f214b0..2fc013dd04 100644 --- a/docs/components/fumadocs/layout/home/client.tsx +++ b/docs/components/fumadocs/layout/home/client.tsx @@ -155,7 +155,7 @@ export function Header({ ))}
-
    +
      {searchToggle.enabled !== false && (searchToggle.components?.sm ?? ( @@ -184,7 +184,7 @@ export function Header({ className="first:mt-4 sm:hidden" /> ))} -
      +
      {menuItems.filter(isSecondary).map((item, i) => ( {/* bg changed from fd-background/80 to white */} - + @@ -233,9 +233,13 @@ function MobileMenuCollapsible(props: ComponentProps) { const [open, setOpen] = useState(false); const onClick = useEffectEvent((e: Event) => { - if (!open) return; + if (!open) { + return; + } const header = document.getElementById("nd-nav"); - if (header && !header.contains(e.target as HTMLElement)) setOpen(false); + if (header && !header.contains(e.target as HTMLElement)) { + setOpen(false); + } }); useEffect(() => { @@ -262,7 +266,9 @@ function MobileMenuCollapsible(props: ComponentProps) { } function isSecondary(item: LinkItemType): boolean { - if ("secondary" in item && item.secondary != null) return item.secondary; + if ("secondary" in item && item.secondary != null) { + return item.secondary; + } return item.type === "icon"; } @@ -292,7 +298,7 @@ function HeaderRoot({ )} {...props} > -
      {children}
      +
      {children}
      )} /> @@ -307,7 +313,9 @@ function NavigationMenuLinkItem({ item: LinkItemType; className?: string; }) { - if (item.type === "custom") return
      {item.children}
      ; + if (item.type === "custom") { + return
      {item.children}
      ; + } if (item.type === "menu") { // modified significantly to make groups @@ -442,8 +450,9 @@ function MobileMenuLinkItem({ item: LinkItemType; className?: string; }) { - if (item.type === "custom") + if (item.type === "custom") { return
      {item.children}
      ; + } const { setOpen } = use(MobileMenuContext)!; if (item.type === "menu") { diff --git a/docs/components/fumadocs/layout/language-toggle.tsx b/docs/components/fumadocs/layout/language-toggle.tsx index 6ecbb2fee1..b733d011ce 100644 --- a/docs/components/fumadocs/layout/language-toggle.tsx +++ b/docs/components/fumadocs/layout/language-toggle.tsx @@ -1,15 +1,17 @@ -'use client'; -import type { ComponentProps } from 'react'; -import { useI18n } from '@fumadocs/base-ui/contexts/i18n'; -import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; -import { cn } from '../../../lib/fumadocs/cn'; -import { buttonVariants } from '../ui/button'; +"use client"; +import type { ComponentProps } from "react"; +import { useI18n } from "@fumadocs/base-ui/contexts/i18n"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; +import { cn } from "../../../lib/fumadocs/cn"; +import { buttonVariants } from "../ui/button"; -export type LanguageSelectProps = ComponentProps<'button'>; +export type LanguageSelectProps = ComponentProps<"button">; export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { const context = useI18n(); - if (!context.locales) throw new Error('Missing ``'); + if (!context.locales) { + throw new Error("Missing ``"); + } return ( @@ -18,8 +20,8 @@ export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { {...props} className={cn( buttonVariants({ - color: 'ghost', - className: 'gap-1.5 p-1.5', + color: "ghost", + className: "gap-1.5 p-1.5", }), props.className, )} @@ -27,7 +29,7 @@ export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { {props.children} -

      +

      {context.text.chooseLanguage}

      {context.locales.map((item) => ( @@ -35,10 +37,10 @@ export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { key={item.locale} type="button" className={cn( - 'p-2 text-start text-sm', + "p-2 text-start text-sm", item.locale === context.locale - ? 'bg-fd-primary/10 font-medium text-fd-primary' - : 'hover:bg-fd-accent hover:text-fd-accent-foreground', + ? "bg-fd-primary/10 font-medium text-fd-primary" + : "hover:bg-fd-accent hover:text-fd-accent-foreground", )} onClick={() => { context.onChange?.(item.locale); @@ -52,9 +54,11 @@ export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { ); } -export function LanguageToggleText(props: ComponentProps<'span'>) { +export function LanguageToggleText(props: ComponentProps<"span">) { const context = useI18n(); - const text = context.locales?.find((item) => item.locale === context.locale)?.name; + const text = context.locales?.find( + (item) => item.locale === context.locale, + )?.name; return {text}; } diff --git a/docs/components/fumadocs/layout/link-item.tsx b/docs/components/fumadocs/layout/link-item.tsx index 8b49b7a00e..7760156cb6 100644 --- a/docs/components/fumadocs/layout/link-item.tsx +++ b/docs/components/fumadocs/layout/link-item.tsx @@ -1,8 +1,8 @@ -'use client'; -import type { ComponentProps, ReactNode } from 'react'; -import { usePathname } from 'fumadocs-core/framework'; -import { isActive } from '../../../lib/fumadocs/urls'; -import Link from 'fumadocs-core/link'; +"use client"; +import type { ComponentProps, ReactNode } from "react"; +import { usePathname } from "fumadocs-core/framework"; +import { isActive } from "../../../lib/fumadocs/urls"; +import Link from "fumadocs-core/link"; interface Filterable { /** @@ -10,7 +10,7 @@ interface Filterable { * * @defaultValue 'all' */ - on?: 'menu' | 'nav' | 'all'; + on?: "menu" | "nav" | "all"; } interface WithHref { @@ -20,12 +20,12 @@ interface WithHref { * * @defaultValue 'url' */ - active?: 'url' | 'nested-url' | 'none'; + active?: "url" | "nested-url" | "none"; external?: boolean; } export interface MainItemType extends WithHref, Filterable { - type?: 'main'; + type?: "main"; icon?: ReactNode; text: ReactNode; description?: ReactNode; @@ -33,7 +33,7 @@ export interface MainItemType extends WithHref, Filterable { } export interface IconItemType extends WithHref, Filterable { - type: 'icon'; + type: "icon"; /** * `aria-label` of icon button */ @@ -47,7 +47,7 @@ export interface IconItemType extends WithHref, Filterable { } export interface ButtonItemType extends WithHref, Filterable { - type: 'button'; + type: "button"; icon?: ReactNode; text: ReactNode; /** @@ -57,7 +57,7 @@ export interface ButtonItemType extends WithHref, Filterable { } export interface MenuItemType extends Partial, Filterable { - type: 'menu'; + type: "menu"; icon?: ReactNode; text: ReactNode; @@ -66,7 +66,7 @@ export interface MenuItemType extends Partial, Filterable { /** * Options when displayed on navigation menu */ - menu?: ComponentProps<'a'> & { + menu?: ComponentProps<"a"> & { banner?: ReactNode; }; }) @@ -80,7 +80,7 @@ export interface MenuItemType extends Partial, Filterable { } export interface CustomItemType extends Filterable { - type: 'custom'; + type: "custom"; /** * @defaultValue false */ @@ -99,13 +99,21 @@ export function LinkItem({ ref, item, ...props -}: Omit, 'href'> & { item: WithHref }) { +}: Omit, "href"> & { item: WithHref }) { const pathname = usePathname(); - const activeType = item.active ?? 'url'; - const active = activeType !== 'none' && isActive(item.url, pathname, activeType === 'nested-url'); + const activeType = item.active ?? "url"; + const active = + activeType !== "none" && + isActive(item.url, pathname, activeType === "nested-url"); return ( - + {props.children} ); diff --git a/docs/components/fumadocs/layout/search-toggle.tsx b/docs/components/fumadocs/layout/search-toggle.tsx index 442fd1ca22..8aa8b0f0ff 100644 --- a/docs/components/fumadocs/layout/search-toggle.tsx +++ b/docs/components/fumadocs/layout/search-toggle.tsx @@ -1,23 +1,26 @@ -'use client'; -import type { ComponentProps } from 'react'; -import { Search } from 'lucide-react'; -import { useSearchContext } from '@fumadocs/base-ui/contexts/search'; -import { useI18n } from '@fumadocs/base-ui/contexts/i18n'; -import { cn } from '../../../lib/fumadocs/cn'; -import { type ButtonProps, buttonVariants } from '../ui/button'; +"use client"; +import type { ComponentProps } from "react"; +import { Search } from "lucide-react"; +import { useSearchContext } from "@fumadocs/base-ui/contexts/search"; +import { useI18n } from "@fumadocs/base-ui/contexts/i18n"; +import { cn } from "../../../lib/fumadocs/cn"; +import { type ButtonProps, buttonVariants } from "../ui/button"; -interface SearchToggleProps extends Omit, 'color'>, ButtonProps { +interface SearchToggleProps + extends Omit, "color">, ButtonProps { hideIfDisabled?: boolean; } export function SearchToggle({ hideIfDisabled, - size = 'icon-sm', - color = 'ghost', + size = "icon-sm", + color = "ghost", ...props }: SearchToggleProps) { const { setOpenSearch, enabled } = useSearchContext(); - if (hideIfDisabled && !enabled) return null; + if (hideIfDisabled && !enabled) { + return null; + } return ( ); } -export function SidebarCollapseTrigger(props: ComponentProps<'button'>) { +export function SidebarCollapseTrigger(props: ComponentProps<"button">) { const { collapsed, setCollapsed } = useSidebar(); return ( @@ -402,14 +471,19 @@ export function SidebarCollapseTrigger(props: ComponentProps<'button'>) { /** * scroll to the element if `active` is true */ -export function useAutoScroll(active: boolean, ref: RefObject) { +export function useAutoScroll( + active: boolean, + ref: RefObject, +) { const { mode } = useSidebar(); useEffect(() => { if (active && ref.current) { scrollIntoView(ref.current, { - boundary: document.getElementById(mode === 'drawer' ? 'nd-sidebar-mobile' : 'nd-sidebar'), - scrollMode: 'if-needed', + boundary: document.getElementById( + mode === "drawer" ? "nd-sidebar-mobile" : "nd-sidebar", + ), + scrollMode: "if-needed", }); } }, [active, mode, ref]); diff --git a/docs/components/fumadocs/layout/sidebar/link-item.tsx b/docs/components/fumadocs/layout/sidebar/link-item.tsx index 564131a41e..95dd4543d4 100644 --- a/docs/components/fumadocs/layout/sidebar/link-item.tsx +++ b/docs/components/fumadocs/layout/sidebar/link-item.tsx @@ -1,14 +1,14 @@ -import type { HTMLAttributes } from 'react'; -import type * as Base from './base'; -import type { LinkItemType } from '../link-item'; +import type { HTMLAttributes } from "react"; +import type * as Base from "./base"; +import type { LinkItemType } from "../link-item"; type InternalComponents = Pick< typeof Base, - | 'SidebarFolder' - | 'SidebarFolderLink' - | 'SidebarFolderContent' - | 'SidebarFolderTrigger' - | 'SidebarItem' + | "SidebarFolder" + | "SidebarFolderLink" + | "SidebarFolderContent" + | "SidebarFolderTrigger" + | "SidebarItem" >; export function createLinkItemRenderer({ @@ -25,11 +25,13 @@ export function createLinkItemRenderer({ item, ...props }: HTMLAttributes & { - item: Exclude; + item: Exclude; }) { - if (item.type === 'custom') return
      {item.children}
      ; + if (item.type === "custom") { + return
      {item.children}
      ; + } - if (item.type === 'menu') + if (item.type === "menu") { return ( {item.url ? ( @@ -50,9 +52,15 @@ export function createLinkItemRenderer({ ); + } return ( - + {item.text} ); diff --git a/docs/components/fumadocs/layout/sidebar/page-tree.tsx b/docs/components/fumadocs/layout/sidebar/page-tree.tsx index 52707689b5..c9f9b89098 100644 --- a/docs/components/fumadocs/layout/sidebar/page-tree.tsx +++ b/docs/components/fumadocs/layout/sidebar/page-tree.tsx @@ -1,7 +1,7 @@ -import { useTreeContext, useTreePath } from '@fumadocs/base-ui/contexts/tree'; -import { type FC, type ReactNode, useMemo, Fragment } from 'react'; -import type * as PageTree from 'fumadocs-core/page-tree'; -import type * as Base from './base'; +import { useTreeContext, useTreePath } from "@fumadocs/base-ui/contexts/tree"; +import { type FC, type ReactNode, useMemo, Fragment } from "react"; +import type * as PageTree from "fumadocs-core/page-tree"; +import type * as Base from "./base"; export interface SidebarPageTreeComponents { Item: FC<{ item: PageTree.Item }>; @@ -11,12 +11,12 @@ export interface SidebarPageTreeComponents { type InternalComponents = Pick< typeof Base, - | 'SidebarSeparator' - | 'SidebarFolder' - | 'SidebarFolderLink' - | 'SidebarFolderContent' - | 'SidebarFolderTrigger' - | 'SidebarItem' + | "SidebarSeparator" + | "SidebarFolder" + | "SidebarFolderLink" + | "SidebarFolderContent" + | "SidebarFolderTrigger" + | "SidebarItem" >; export function createPageTreeRenderer({ @@ -27,7 +27,13 @@ export function createPageTreeRenderer({ SidebarSeparator, SidebarItem, }: InternalComponents) { - function PageTreeFolder({ item, children }: { item: PageTree.Folder; children: ReactNode }) { + function PageTreeFolder({ + item, + children, + }: { + item: PageTree.Folder; + children: ReactNode; + }) { const path = useTreePath(); return ( @@ -37,7 +43,10 @@ export function createPageTreeRenderer({ defaultOpen={item.defaultOpen} > {item.index ? ( - + {item.icon} {item.name} @@ -55,15 +64,19 @@ export function createPageTreeRenderer({ /** * Render sidebar items from page tree */ - return function SidebarPageTree(components: Partial) { + return function SidebarPageTree( + components: Partial, + ) { const { root } = useTreeContext(); const { Separator, Item, Folder = PageTreeFolder } = components; return useMemo(() => { function renderSidebarList(items: PageTree.Node[]) { return items.map((item, i) => { - if (item.type === 'separator') { - if (Separator) return ; + if (item.type === "separator") { + if (Separator) { + return ; + } return ( {item.icon} @@ -72,7 +85,7 @@ export function createPageTreeRenderer({ ); } - if (item.type === 'folder') { + if (item.type === "folder") { return ( {renderSidebarList(item.children)} @@ -80,16 +93,25 @@ export function createPageTreeRenderer({ ); } - if (Item) return ; + if (Item) { + return ; + } return ( - + {item.name} ); }); } - return {renderSidebarList(root.children)}; + return ( + {renderSidebarList(root.children)} + ); }, [Folder, Item, Separator, root]); }; } diff --git a/docs/components/fumadocs/layout/sidebar/tabs/dropdown.tsx b/docs/components/fumadocs/layout/sidebar/tabs/dropdown.tsx index 66ed68c83a..20ce10c509 100644 --- a/docs/components/fumadocs/layout/sidebar/tabs/dropdown.tsx +++ b/docs/components/fumadocs/layout/sidebar/tabs/dropdown.tsx @@ -1,16 +1,16 @@ -'use client'; -import { Check, ChevronsUpDown } from 'lucide-react'; -import { type ComponentProps, type ReactNode, useMemo, useState } from 'react'; -import Link from 'fumadocs-core/link'; -import { usePathname } from 'fumadocs-core/framework'; -import { cn } from '../../../../../lib/fumadocs/cn'; -import { normalize, isActive } from '../../../../../lib/fumadocs/urls'; -import { useSidebar } from '../base'; -import { Popover, PopoverContent, PopoverTrigger } from '../../../ui/popover'; -import type { SidebarTab } from './index'; +"use client"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { type ComponentProps, type ReactNode, useMemo, useState } from "react"; +import Link from "fumadocs-core/link"; +import { usePathname } from "fumadocs-core/framework"; +import { cn } from "../../../../../lib/fumadocs/cn"; +import { normalize, isActive } from "../../../../../lib/fumadocs/urls"; +import { useSidebar } from "../base"; +import { Popover, PopoverContent, PopoverTrigger } from "../../../ui/popover"; +import type { SidebarTab } from "./index"; export interface SidebarTabWithProps extends SidebarTab { - props?: ComponentProps<'a'>; + props?: ComponentProps<"a">; } export function SidebarTabsDropdown({ @@ -20,7 +20,7 @@ export function SidebarTabsDropdown({ }: { placeholder?: ReactNode; options: SidebarTabWithProps[]; -} & ComponentProps<'button'>) { +} & ComponentProps<"button">) { const [open, setOpen] = useState(false); const { closeOnRedirect } = useSidebar(); const pathname = usePathname(); @@ -36,10 +36,12 @@ export function SidebarTabsDropdown({ const item = selected ? ( <> -
      {selected.icon}
      +
      + {selected.icon} +

      {selected.title}

      -

      +

      {selected.description}

      @@ -54,18 +56,20 @@ export function SidebarTabsDropdown({ {item} - + )} - + {options.map((item) => { const isActive = selected && item.url === selected.url; - if (!isActive && item.unlisted) return; + if (!isActive && item.unlisted) { + return; + } return ( -
      {item.icon}
      +
      + {item.icon} +
      -

      {item.title}

      -

      +

      {item.title}

      +

      {item.description}

      @@ -101,7 +107,9 @@ export function SidebarTabsDropdown({ } export function isTabActive(tab: SidebarTab, pathname: string) { - if (tab.urls) return tab.urls.has(normalize(pathname)); + if (tab.urls) { + return tab.urls.has(normalize(pathname)); + } return isActive(tab.url, pathname, true); } diff --git a/docs/components/fumadocs/layout/sidebar/tabs/index.tsx b/docs/components/fumadocs/layout/sidebar/tabs/index.tsx index 5f6b7e1b12..8b95dd290e 100644 --- a/docs/components/fumadocs/layout/sidebar/tabs/index.tsx +++ b/docs/components/fumadocs/layout/sidebar/tabs/index.tsx @@ -1,5 +1,5 @@ -import type * as PageTree from 'fumadocs-core/page-tree'; -import type { ReactNode } from 'react'; +import type * as PageTree from "fumadocs-core/page-tree"; +import type { ReactNode } from "react"; export interface SidebarTab { /** @@ -22,13 +22,15 @@ export interface GetSidebarTabsOptions { transform?: (option: SidebarTab, node: PageTree.Folder) => SidebarTab | null; } -const defaultTransform: GetSidebarTabsOptions['transform'] = (option, node) => { - if (!node.icon) return option; +const defaultTransform: GetSidebarTabsOptions["transform"] = (option, node) => { + if (!node.icon) { + return option; + } return { ...option, icon: ( -
      +
      {node.icon}
      ), @@ -41,13 +43,16 @@ export function getSidebarTabs( ): SidebarTab[] { const results: SidebarTab[] = []; - function scanOptions(node: PageTree.Root | PageTree.Folder, unlisted?: boolean) { - if ('root' in node && node.root) { + function scanOptions( + node: PageTree.Root | PageTree.Folder, + unlisted?: boolean, + ) { + if ("root" in node && node.root) { const urls = getFolderUrls(node); if (urls.size > 0) { const option: SidebarTab = { - url: urls.values().next().value ?? '', + url: urls.values().next().value ?? "", title: node.name, icon: node.icon, unlisted, @@ -56,27 +61,42 @@ export function getSidebarTabs( }; const mapped = transform ? transform(option, node) : option; - if (mapped) results.push(mapped); + if (mapped) { + results.push(mapped); + } } } for (const child of node.children) { - if (child.type === 'folder') scanOptions(child, unlisted); + if (child.type === "folder") { + scanOptions(child, unlisted); + } } } scanOptions(tree); - if (tree.fallback) scanOptions(tree.fallback, true); + if (tree.fallback) { + scanOptions(tree.fallback, true); + } return results; } -function getFolderUrls(folder: PageTree.Folder, output: Set = new Set()): Set { - if (folder.index) output.add(folder.index.url); +function getFolderUrls( + folder: PageTree.Folder, + output: Set = new Set(), +): Set { + if (folder.index) { + output.add(folder.index.url); + } for (const child of folder.children) { - if (child.type === 'page' && !child.external) output.add(child.url); - if (child.type === 'folder') getFolderUrls(child, output); + if (child.type === "page" && !child.external) { + output.add(child.url); + } + if (child.type === "folder") { + getFolderUrls(child, output); + } } return output; diff --git a/docs/components/fumadocs/layout/theme-toggle.tsx b/docs/components/fumadocs/layout/theme-toggle.tsx index 5f8c8b0f55..3aad40e276 100644 --- a/docs/components/fumadocs/layout/theme-toggle.tsx +++ b/docs/components/fumadocs/layout/theme-toggle.tsx @@ -1,27 +1,34 @@ -'use client'; -import { cva } from 'class-variance-authority'; -import { Airplay, Moon, Sun } from 'lucide-react'; -import { useTheme } from 'next-themes'; -import { ComponentProps, useEffect, useState } from 'react'; -import { cn } from '../../../lib/fumadocs/cn'; +"use client"; +import { cva } from "class-variance-authority"; +import { Airplay, Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; +import { ComponentProps, useEffect, useState } from "react"; +import { cn } from "../../../lib/fumadocs/cn"; -const itemVariants = cva('size-6.5 rounded-full p-1.5 text-fd-muted-foreground', { - variants: { - active: { - true: 'bg-fd-accent text-fd-accent-foreground', - false: 'text-fd-muted-foreground', +const itemVariants = cva( + "size-6.5 rounded-full p-1.5 text-fd-muted-foreground", + { + variants: { + active: { + true: "bg-fd-accent text-fd-accent-foreground", + false: "text-fd-muted-foreground", + }, }, }, -}); +); -const full = [['light', Sun] as const, ['dark', Moon] as const, ['system', Airplay] as const]; +const full = [ + ["light", Sun] as const, + ["dark", Moon] as const, + ["system", Airplay] as const, +]; export function ThemeToggle({ className, - mode = 'light-dark', + mode = "light-dark", ...props -}: ComponentProps<'div'> & { - mode?: 'light-dark' | 'light-dark-system'; +}: ComponentProps<"div"> & { + mode?: "light-dark" | "light-dark-system"; }) { const { setTheme, theme, resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); @@ -30,20 +37,25 @@ export function ThemeToggle({ setMounted(true); }, []); - const container = cn('inline-flex items-center rounded-full border p-1', className); + const container = cn( + "inline-flex items-center rounded-full border p-1", + className, + ); - if (mode === 'light-dark') { + if (mode === "light-dark") { const value = mounted ? resolvedTheme : null; return ( +
      +
      + ); +} diff --git a/examples/03-ui-components/18-drag-n-drop/src/styles.css b/examples/03-ui-components/18-drag-n-drop/src/styles.css new file mode 100644 index 0000000000..4c8d78b9f4 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/src/styles.css @@ -0,0 +1,154 @@ +.app-container { + display: flex; + gap: 2rem; + padding: 2rem; + max-width: 1400px; + margin: 0 auto; + height: 100vh; +} + +.editor-section { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.editor-section h2 { + margin-top: 0; + margin-bottom: 1rem; + font-size: 1.5rem; + color: #333; +} + +.drag-demo-section { + flex: 0 0 400px; + display: flex; + flex-direction: column; + background: #f8f9fa; + border-radius: 8px; + padding: 1.5rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.drag-demo-section h2 { + margin-top: 0; + margin-bottom: 0.5rem; + font-size: 1.5rem; + color: #333; +} + +.info-text { + font-size: 0.875rem; + color: #666; + margin-bottom: 1.5rem; + line-height: 1.5; +} + +.info-text code { + background: #e9ecef; + padding: 0.125rem 0.375rem; + border-radius: 3px; + font-family: monospace; + font-size: 0.85em; + color: #c7254e; +} + +.drag-columns { + display: flex; + gap: 1rem; + flex: 1; + min-height: 0; +} + +.drag-column { + flex: 1; + display: flex; + flex-direction: column; +} + +.drag-column h3 { + margin-top: 0; + margin-bottom: 0.75rem; + font-size: 1rem; + color: #495057; + font-weight: 600; +} + +.items-container { + flex: 1; + background: white; + border: 2px dashed #dee2e6; + border-radius: 6px; + padding: 0.75rem; + min-height: 200px; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.drop-zone .items-container { + border-color: #4ecdc4; + background: #f0fffe; +} + +.draggable-item { + padding: 0.75rem 1rem; + border-radius: 6px; + color: white; + font-weight: 500; + cursor: move; + user-select: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + transition: + transform 0.2s, + box-shadow 0.2s; +} + +.draggable-item:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +.draggable-item:active { + cursor: grabbing; + transform: scale(0.95); +} + +.placeholder { + color: #adb5bd; + text-align: center; + margin: auto; + font-style: italic; +} + +.reset-button { + margin-top: 1rem; + padding: 0.625rem 1.25rem; + background: #495057; + color: white; + border: none; + border-radius: 6px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; +} + +.reset-button:hover { + background: #343a40; +} + +.reset-button:active { + transform: scale(0.98); +} + +@media (max-width: 1024px) { + .app-container { + flex-direction: column; + height: auto; + } + + .drag-demo-section { + flex: 0 0 auto; + } +} diff --git a/examples/03-ui-components/18-drag-n-drop/tsconfig.json b/examples/03-ui-components/18-drag-n-drop/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__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 + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} 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 new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/vite.config.ts @@ -0,0 +1,31 @@ +// 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-plus"; +// 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")) + ? {} + : ({ + // 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/03-ui-components/19-suggestion-menus-grouping-ordering/.bnexample.json b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/.bnexample.json new file mode 100644 index 0000000000..8aa4573b06 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/.bnexample.json @@ -0,0 +1,12 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Blocks", + "UI Components", + "Suggestion Menus", + "Slash Menu" + ] +} diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/README.md b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/README.md new file mode 100644 index 0000000000..76f3061571 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/README.md @@ -0,0 +1,11 @@ +# Slash Menu Grouping & Ordering + +In this example, we filter and reorder the default Slash Menu items so that only the "Basic blocks" and "Headings" groups are shown, with "Basic blocks" appearing first. + +**Try it out:** Press the "/" key to open the Slash Menu and see the reordered groups! + +**Relevant Docs:** + +- [Item Grouping & Ordering](/docs/react/components/suggestion-menus) +- [Changing Slash Menu Items](/docs/react/components/suggestion-menus) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/index.html b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/index.html new file mode 100644 index 0000000000..405a9fc360 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/index.html @@ -0,0 +1,14 @@ + + + + + Slash Menu Grouping & Ordering + + + +
      + + + diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/main.tsx b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/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/03-ui-components/19-suggestion-menus-grouping-ordering/package.json b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/package.json new file mode 100644 index 0000000000..e870d856d4 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-suggestion-menus-grouping-ordering", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp 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" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/src/App.tsx b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/src/App.tsx new file mode 100644 index 0000000000..93480ff122 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/src/App.tsx @@ -0,0 +1,60 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + DefaultReactSuggestionItem, + getDefaultReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; + +// Returns the default Slash Menu items, keeping only the "Basic blocks" and +// "Headings" groups, with "Basic blocks" listed before "Headings". +const getCustomSlashMenuItems = ( + editor: BlockNoteEditor, +): DefaultReactSuggestionItem[] => { + const defaultItems = getDefaultReactSlashMenuItems(editor); + + const basicBlocks = defaultItems.filter( + (item) => item.group === "Basic blocks", + ); + const headings = defaultItems.filter((item) => item.group === "Headings"); + + return [...basicBlocks, ...headings]; +}; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Press the '/' key to open the Slash Menu", + }, + { + type: "paragraph", + content: + "Notice that only 'Basic blocks' and 'Headings' are shown, in that order", + }, + ], + }); + + // Renders the editor instance. + return ( + + + filterSuggestionItems(getCustomSlashMenuItems(editor), query) + } + /> + + ); +} 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 new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__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 + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} 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 new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts @@ -0,0 +1,31 @@ +// 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-plus"; +// 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")) + ? {} + : ({ + // 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/03-ui-components/20-portal-elements/.bnexample.json b/examples/03-ui-components/20-portal-elements/.bnexample.json new file mode 100644 index 0000000000..40dfffd4d9 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["UI Components", "Advanced"] +} diff --git a/examples/03-ui-components/20-portal-elements/README.md b/examples/03-ui-components/20-portal-elements/README.md new file mode 100644 index 0000000000..38a5926e3c --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/README.md @@ -0,0 +1,13 @@ +# Configuring Portal Targets + +By default, BlockNote's floating UI elements (formatting toolbar, slash menu, table handles, etc.) mount inside the editor's `bn-container`. The `portalElements` prop on `BlockNoteView` lets you change that — globally via `default`, or per element by key. + +This example renders two editors side-by-side, both wrapped in a small `overflow: hidden` container. The left editor uses the default — the slash menu is clipped by the editor's bounds. The right editor passes `portalElements={{ default: document.body }}` so floating UI escapes the wrapper and renders fully. + +```tsx + +``` + +**Relevant Docs:** + +- [UI Components](/docs/react/components) diff --git a/examples/03-ui-components/20-portal-elements/index.html b/examples/03-ui-components/20-portal-elements/index.html new file mode 100644 index 0000000000..e43d537d45 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/index.html @@ -0,0 +1,14 @@ + + + + + Configuring Portal Targets + + + +
      + + + diff --git a/examples/03-ui-components/20-portal-elements/main.tsx b/examples/03-ui-components/20-portal-elements/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/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/03-ui-components/20-portal-elements/package.json b/examples/03-ui-components/20-portal-elements/package.json new file mode 100644 index 0000000000..d4563dc4fe --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-portal-elements", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp 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" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/20-portal-elements/src/App.tsx b/examples/03-ui-components/20-portal-elements/src/App.tsx new file mode 100644 index 0000000000..f450fac7e1 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/src/App.tsx @@ -0,0 +1,57 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote, type PortalElementsMap } from "@blocknote/react"; + +import "./styles.css"; + +const initialContent = [ + { + type: "paragraph" as const, + content: "Click in this editor and press / to open the slash menu.", + }, + { + type: "paragraph" as const, + content: "Notice whether the menu fits inside the box or escapes it.", + }, + { + type: "paragraph" as const, + }, +]; + +function PortalDemoEditor({ + label, + description, + portalElements, +}: { + label: string; + description: string; + portalElements?: PortalElementsMap; +}) { + const editor = useCreateBlockNote({ initialContent }); + return ( +
      +
      {label}
      +
      {description}
      +
      + +
      +
      + ); +} + +export default function App() { + return ( +
      + + +
      + ); +} diff --git a/examples/03-ui-components/20-portal-elements/src/styles.css b/examples/03-ui-components/20-portal-elements/src/styles.css new file mode 100644 index 0000000000..8cf28385d0 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/src/styles.css @@ -0,0 +1,68 @@ +.views { + container-name: views; + container-type: inline-size; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 8px; + padding: 8px; +} + +/* + * Each view is intentionally shorter than the slash menu so the clipping + * vs escaping behaviour is visible at a glance. + */ +.view-wrapper { + display: flex; + flex-direction: column; + height: 260px; + width: 100%; +} + +@container views (width > 1024px) { + .view-wrapper { + width: calc(50% - 4px); + } +} + +.view-label { + color: #0090ff; + display: flex; + font-size: 12px; + font-weight: bold; + justify-content: space-between; + margin-inline: 16px; +} + +.view-description { + color: #0090ff; + font-size: 12px; + margin: 2px 16px 0; +} + +/* + * `position: relative` is what actually makes `overflow: hidden` clip the + * absolutely-positioned floating UI. Without it the popover's containing + * block is the viewport and the clip is bypassed. + */ +.view { + border: solid #0090ff 1px; + border-radius: 16px; + flex: 1; + height: 0; + padding: 8px; + position: relative; + overflow: hidden; +} + +.view .bn-container { + height: 100%; + margin: 0; + max-width: none; + padding: 0; +} + +.view .bn-editor { + height: 100%; + overflow: auto; +} diff --git a/examples/03-ui-components/20-portal-elements/tsconfig.json b/examples/03-ui-components/20-portal-elements/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__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 + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/20-portal-elements/vite.config.ts b/examples/03-ui-components/20-portal-elements/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/vite.config.ts @@ -0,0 +1,31 @@ +// 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-plus"; +// 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")) + ? {} + : ({ + // 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/04-theming/01-theming-dom-attributes/main.tsx b/examples/04-theming/01-theming-dom-attributes/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/04-theming/01-theming-dom-attributes/main.tsx +++ b/examples/04-theming/01-theming-dom-attributes/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/04-theming/01-theming-dom-attributes/package.json b/examples/04-theming/01-theming-dom-attributes/package.json index e507bf0891..8a70d4f0ef 100644 --- a/examples/04-theming/01-theming-dom-attributes/package.json +++ b/examples/04-theming/01-theming-dom-attributes/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/04-theming/01-theming-dom-attributes/src/App.tsx b/examples/04-theming/01-theming-dom-attributes/src/App.tsx index 7068e12388..ccf55a3d5f 100644 --- a/examples/04-theming/01-theming-dom-attributes/src/App.tsx +++ b/examples/04-theming/01-theming-dom-attributes/src/App.tsx @@ -44,9 +44,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/01-theming-dom-attributes/tsconfig.json b/examples/04-theming/01-theming-dom-attributes/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/04-theming/01-theming-dom-attributes/tsconfig.json +++ b/examples/04-theming/01-theming-dom-attributes/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/04-theming/01-theming-dom-attributes/vite.config.ts +++ b/examples/04-theming/01-theming-dom-attributes/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/04-theming/02-changing-font/main.tsx b/examples/04-theming/02-changing-font/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/04-theming/02-changing-font/main.tsx +++ b/examples/04-theming/02-changing-font/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/04-theming/02-changing-font/package.json b/examples/04-theming/02-changing-font/package.json index 012ea4a389..6c00cfc8f8 100644 --- a/examples/04-theming/02-changing-font/package.json +++ b/examples/04-theming/02-changing-font/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/04-theming/02-changing-font/src/App.tsx b/examples/04-theming/02-changing-font/src/App.tsx index 4ef3f9a3dd..3626ef9e32 100644 --- a/examples/04-theming/02-changing-font/src/App.tsx +++ b/examples/04-theming/02-changing-font/src/App.tsx @@ -17,9 +17,6 @@ export default function App() { type: "paragraph", content: "You'll see that the font has been changed to Comic Sans MS", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/02-changing-font/src/styles.css b/examples/04-theming/02-changing-font/src/styles.css index 49cad5e5a2..b21029720a 100644 --- a/examples/04-theming/02-changing-font/src/styles.css +++ b/examples/04-theming/02-changing-font/src/styles.css @@ -1,3 +1,3 @@ -.bn-container[data-changing-font-demo] .bn-editor * { +.bn-root[data-changing-font-demo] .bn-editor * { font-family: "Comic Sans MS", sans-serif; } diff --git a/examples/04-theming/02-changing-font/tsconfig.json b/examples/04-theming/02-changing-font/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/04-theming/02-changing-font/tsconfig.json +++ b/examples/04-theming/02-changing-font/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/04-theming/02-changing-font/vite.config.ts b/examples/04-theming/02-changing-font/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/04-theming/02-changing-font/vite.config.ts +++ b/examples/04-theming/02-changing-font/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/04-theming/03-theming-css/main.tsx b/examples/04-theming/03-theming-css/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/04-theming/03-theming-css/main.tsx +++ b/examples/04-theming/03-theming-css/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/04-theming/03-theming-css/package.json b/examples/04-theming/03-theming-css/package.json index 946f29cfab..9415cbbd2e 100644 --- a/examples/04-theming/03-theming-css/package.json +++ b/examples/04-theming/03-theming-css/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/04-theming/03-theming-css/src/App.tsx b/examples/04-theming/03-theming-css/src/App.tsx index 493ae7d4ed..b15568d810 100644 --- a/examples/04-theming/03-theming-css/src/App.tsx +++ b/examples/04-theming/03-theming-css/src/App.tsx @@ -22,9 +22,6 @@ export default function App() { content: "Press the '/' key - the hovered Slash Menu items are also blue", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/03-theming-css/src/styles.css b/examples/04-theming/03-theming-css/src/styles.css index 8909238e5f..4227097f66 100644 --- a/examples/04-theming/03-theming-css/src/styles.css +++ b/examples/04-theming/03-theming-css/src/styles.css @@ -1,11 +1,10 @@ /* Adds border and shadow to editor */ -.bn-container[data-theming-css-demo] .bn-editor * { +.bn-root[data-theming-css-demo] .bn-editor * { color: blue; } /* Makes slash menu hovered items blue */ -.bn-container[data-theming-css-demo] - .bn-suggestion-menu-item[aria-selected="true"], -.bn-container[data-theming-css-demo] .bn-suggestion-menu-item:hover { +.bn-root[data-theming-css-demo] .bn-suggestion-menu-item[aria-selected="true"], +.bn-root[data-theming-css-demo] .bn-suggestion-menu-item:hover { background-color: blue; } diff --git a/examples/04-theming/03-theming-css/tsconfig.json b/examples/04-theming/03-theming-css/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/04-theming/03-theming-css/tsconfig.json +++ b/examples/04-theming/03-theming-css/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/04-theming/03-theming-css/vite.config.ts b/examples/04-theming/03-theming-css/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/04-theming/03-theming-css/vite.config.ts +++ b/examples/04-theming/03-theming-css/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/04-theming/04-theming-css-variables/main.tsx b/examples/04-theming/04-theming-css-variables/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/04-theming/04-theming-css-variables/main.tsx +++ b/examples/04-theming/04-theming-css-variables/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/04-theming/04-theming-css-variables/package.json b/examples/04-theming/04-theming-css-variables/package.json index 2d3fb8fd37..2d88ea102e 100644 --- a/examples/04-theming/04-theming-css-variables/package.json +++ b/examples/04-theming/04-theming-css-variables/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/04-theming/04-theming-css-variables/src/App.tsx b/examples/04-theming/04-theming-css-variables/src/App.tsx index ba45053f04..7ef5ab3f6d 100644 --- a/examples/04-theming/04-theming-css-variables/src/App.tsx +++ b/examples/04-theming/04-theming-css-variables/src/App.tsx @@ -22,9 +22,6 @@ export default function App() { content: "Toggle light/dark mode in the page footer and see the theme change too", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/04-theming-css-variables/src/styles.css b/examples/04-theming/04-theming-css-variables/src/styles.css index 59301c371b..aab8f08d08 100644 --- a/examples/04-theming/04-theming-css-variables/src/styles.css +++ b/examples/04-theming/04-theming-css-variables/src/styles.css @@ -1,5 +1,5 @@ /* Base theme */ -.bn-container[data-theming-css-variables-demo][data-color-scheme] { +.bn-root[data-theming-css-variables-demo][data-color-scheme] { --bn-colors-editor-text: #222222; --bn-colors-editor-background: #ffeeee; --bn-colors-menu-text: #ffffff; @@ -21,7 +21,7 @@ } /* Changes for dark mode */ -.bn-container[data-theming-css-variables-demo][data-color-scheme="dark"] { +.bn-root[data-theming-css-variables-demo][data-color-scheme="dark"] { --bn-colors-editor-text: #ffffff; --bn-colors-editor-background: #9b0000; --bn-colors-side-menu: #ffffff; diff --git a/examples/04-theming/04-theming-css-variables/tsconfig.json b/examples/04-theming/04-theming-css-variables/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/04-theming/04-theming-css-variables/tsconfig.json +++ b/examples/04-theming/04-theming-css-variables/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/04-theming/04-theming-css-variables/vite.config.ts +++ b/examples/04-theming/04-theming-css-variables/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/04-theming/05-theming-css-variables-code/main.tsx b/examples/04-theming/05-theming-css-variables-code/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/04-theming/05-theming-css-variables-code/main.tsx +++ b/examples/04-theming/05-theming-css-variables-code/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/04-theming/05-theming-css-variables-code/package.json b/examples/04-theming/05-theming-css-variables-code/package.json index e391fc873d..49752ab4f5 100644 --- a/examples/04-theming/05-theming-css-variables-code/package.json +++ b/examples/04-theming/05-theming-css-variables-code/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/04-theming/05-theming-css-variables-code/src/App.tsx b/examples/04-theming/05-theming-css-variables-code/src/App.tsx index 8a02496d7e..0eaa83cfa1 100644 --- a/examples/04-theming/05-theming-css-variables-code/src/App.tsx +++ b/examples/04-theming/05-theming-css-variables-code/src/App.tsx @@ -84,9 +84,6 @@ export default function App() { content: "Toggle light/dark mode in the page footer and see the theme change too", }, - { - type: "paragraph", - }, ], }); 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/04-theming/05-theming-css-variables-code/tsconfig.json +++ b/examples/04-theming/05-theming-css-variables-code/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/04-theming/06-code-block/main.tsx b/examples/04-theming/06-code-block/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/04-theming/06-code-block/main.tsx +++ b/examples/04-theming/06-code-block/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/04-theming/06-code-block/package.json b/examples/04-theming/06-code-block/package.json index d1122bd3ac..4a8daf8f88 100644 --- a/examples/04-theming/06-code-block/package.json +++ b/examples/04-theming/06-code-block/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/code-block": "latest" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/04-theming/06-code-block/src/App.tsx b/examples/04-theming/06-code-block/src/App.tsx index 6111070e37..82d10bae9e 100644 --- a/examples/04-theming/06-code-block/src/App.tsx +++ b/examples/04-theming/06-code-block/src/App.tsx @@ -47,9 +47,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/06-code-block/tsconfig.json b/examples/04-theming/06-code-block/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/04-theming/06-code-block/tsconfig.json +++ b/examples/04-theming/06-code-block/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/04-theming/06-code-block/vite.config.ts b/examples/04-theming/06-code-block/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/04-theming/06-code-block/vite.config.ts +++ b/examples/04-theming/06-code-block/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/04-theming/07-custom-code-block/.bnexample.json b/examples/04-theming/07-custom-code-block/.bnexample.json index 5776d2de67..84166710e3 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": "^3.19.0", - "@shikijs/engine-javascript": "^3.19.0", - "@shikijs/langs-precompiled": "^3.19.0", - "@shikijs/themes": "^3.19.0", - "@shikijs/types": "^3.19.0" + "@shikijs/core": "^4", + "@shikijs/engine-javascript": "^4", + "@shikijs/langs-precompiled": "^4", + "@shikijs/themes": "^4", + "@shikijs/types": "^4" } } diff --git a/examples/04-theming/07-custom-code-block/main.tsx b/examples/04-theming/07-custom-code-block/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/04-theming/07-custom-code-block/main.tsx +++ b/examples/04-theming/07-custom-code-block/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/04-theming/07-custom-code-block/package.json b/examples/04-theming/07-custom-code-block/package.json index 153b1c160c..0c944343a5 100644 --- a/examples/04-theming/07-custom-code-block/package.json +++ b/examples/04-theming/07-custom-code-block/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,22 +16,21 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/code-block": "latest", - "@shikijs/core": "^3.19.0", - "@shikijs/engine-javascript": "^3.19.0", - "@shikijs/langs-precompiled": "^3.19.0", - "@shikijs/themes": "^3.19.0", - "@shikijs/types": "^3.19.0" + "@shikijs/core": "^4", + "@shikijs/engine-javascript": "^4", + "@shikijs/langs-precompiled": "^4", + "@shikijs/themes": "^4", + "@shikijs/types": "^4" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 32adb82472..8a9c74eac1 100644 --- a/examples/04-theming/07-custom-code-block/src/App.tsx +++ b/examples/04-theming/07-custom-code-block/src/App.tsx @@ -69,9 +69,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts b/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts index b596fe2029..de16ace3f8 100644 --- a/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts +++ b/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts @@ -4,7 +4,7 @@ import type { DynamicImportThemeRegistration, HighlighterGeneric, } from "@shikijs/types"; -import { createdBundledHighlighter } from "@shikijs/core"; +import { createBundledHighlighter } from "@shikijs/core"; import { createJavaScriptRegexEngine } from "@shikijs/engine-javascript"; type BundledLanguage = "typescript" | "ts" | "javascript" | "js" | "vue"; @@ -24,7 +24,7 @@ const bundledThemes = { "dark-plus": () => import("@shikijs/themes/dark-plus"), } as Record; -const createHighlighter = /* @__PURE__ */ createdBundledHighlighter< +const createHighlighter = /* @__PURE__ */ createBundledHighlighter< BundledLanguage, BundledTheme >({ diff --git a/examples/04-theming/07-custom-code-block/tsconfig.json b/examples/04-theming/07-custom-code-block/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/04-theming/07-custom-code-block/tsconfig.json +++ b/examples/04-theming/07-custom-code-block/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/04-theming/07-custom-code-block/vite.config.ts +++ b/examples/04-theming/07-custom-code-block/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/01-converting-blocks-to-html/main.tsx b/examples/05-interoperability/01-converting-blocks-to-html/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/main.tsx +++ b/examples/05-interoperability/01-converting-blocks-to-html/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/05-interoperability/01-converting-blocks-to-html/package.json b/examples/05-interoperability/01-converting-blocks-to-html/package.json index 0f0316000f..850cf800b6 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/package.json +++ b/examples/05-interoperability/01-converting-blocks-to-html/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/tsconfig.json +++ b/examples/05-interoperability/01-converting-blocks-to-html/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/02-converting-blocks-from-html/main.tsx b/examples/05-interoperability/02-converting-blocks-from-html/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/main.tsx +++ b/examples/05-interoperability/02-converting-blocks-from-html/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/05-interoperability/02-converting-blocks-from-html/package.json b/examples/05-interoperability/02-converting-blocks-from-html/package.json index d62b8e0f42..b097113f19 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/package.json +++ b/examples/05-interoperability/02-converting-blocks-from-html/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/tsconfig.json +++ b/examples/05-interoperability/02-converting-blocks-from-html/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/03-converting-blocks-to-md/main.tsx b/examples/05-interoperability/03-converting-blocks-to-md/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/main.tsx +++ b/examples/05-interoperability/03-converting-blocks-to-md/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/05-interoperability/03-converting-blocks-to-md/package.json b/examples/05-interoperability/03-converting-blocks-to-md/package.json index f6b0651c0d..7b3e9c2575 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/package.json +++ b/examples/05-interoperability/03-converting-blocks-to-md/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/tsconfig.json +++ b/examples/05-interoperability/03-converting-blocks-to-md/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/04-converting-blocks-from-md/main.tsx b/examples/05-interoperability/04-converting-blocks-from-md/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/main.tsx +++ b/examples/05-interoperability/04-converting-blocks-from-md/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/05-interoperability/04-converting-blocks-from-md/package.json b/examples/05-interoperability/04-converting-blocks-from-md/package.json index 3ca496c50d..7a6300deb6 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/package.json +++ b/examples/05-interoperability/04-converting-blocks-from-md/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/tsconfig.json +++ b/examples/05-interoperability/04-converting-blocks-from-md/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/main.tsx b/examples/05-interoperability/05-converting-blocks-to-pdf/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/main.tsx +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); 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 3af24b9f75..73bb3589ab 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-pdf-exporter": "latest", @@ -28,7 +27,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 7a34932ec8..93d1c04a31 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 @@ -226,16 +226,15 @@ export default function App() { { type: "image", props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/tsconfig.json +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/main.tsx b/examples/05-interoperability/06-converting-blocks-to-docx/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/main.tsx +++ b/examples/05-interoperability/06-converting-blocks-to-docx/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); 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 af8c76e04b..4d8be18e97 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/package.json +++ b/examples/05-interoperability/06-converting-blocks-to-docx/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-docx-exporter": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 8c5ae2398f..4b873d9a40 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 @@ -220,16 +220,15 @@ export default function App() { { type: "image", props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/tsconfig.json +++ b/examples/05-interoperability/06-converting-blocks-to-docx/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/main.tsx b/examples/05-interoperability/07-converting-blocks-to-odt/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/main.tsx +++ b/examples/05-interoperability/07-converting-blocks-to-odt/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); 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 aae892d407..3877e20d75 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/package.json +++ b/examples/05-interoperability/07-converting-blocks-to-odt/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-odt-exporter": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 fcfa07ff85..7b884ac658 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 @@ -220,16 +220,15 @@ export default function App() { { type: "image", props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/tsconfig.json +++ b/examples/05-interoperability/07-converting-blocks-to-odt/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/main.tsx b/examples/05-interoperability/08-converting-blocks-to-react-email/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/main.tsx +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); 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 10e8836f84..1112a09710 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 @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-email-exporter": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 5d3d896b8f..3ba0dddea4 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 @@ -216,16 +216,15 @@ export default function App() { { type: "image", props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, 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 dbe3e6f62d..93fa81bee8 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 @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/09-blocks-to-html-static-render/main.tsx b/examples/05-interoperability/09-blocks-to-html-static-render/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/09-blocks-to-html-static-render/main.tsx +++ b/examples/05-interoperability/09-blocks-to-html-static-render/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/05-interoperability/09-blocks-to-html-static-render/package.json b/examples/05-interoperability/09-blocks-to-html-static-render/package.json index 52926421b4..898e0c734a 100644 --- a/examples/05-interoperability/09-blocks-to-html-static-render/package.json +++ b/examples/05-interoperability/09-blocks-to-html-static-render/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx b/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx index bd3e3b4c7c..f166b6d154 100644 --- a/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx +++ b/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx @@ -90,9 +90,8 @@ export default function App() { { type: "image", props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { @@ -149,9 +148,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); @@ -183,7 +179,7 @@ export default function App() {
      Rendered Static HTML Output
      - {/* To make the static HTML look identical to the editor, we need to + {/* To make the static HTML look identical to the editor, we need to add these two wrapping divs to the exported blocks. These mock the wrapping elements of a BlockNote editor, and are needed as the exported HTML only holds the contents of the editor. You need will @@ -192,7 +188,7 @@ export default function App() { etc. It's easiest to just copy the class names and HTML attributes from an actual BlockNote editor. */}
      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 dbe3e6f62d..93fa81bee8 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 @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/05-interoperability/10-static-html-render/main.tsx b/examples/05-interoperability/10-static-html-render/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/05-interoperability/10-static-html-render/main.tsx +++ b/examples/05-interoperability/10-static-html-render/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/05-interoperability/10-static-html-render/package.json b/examples/05-interoperability/10-static-html-render/package.json index 6343ac6efc..03d1cadcf1 100644 --- a/examples/05-interoperability/10-static-html-render/package.json +++ b/examples/05-interoperability/10-static-html-render/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/05-interoperability/10-static-html-render/src/App.tsx b/examples/05-interoperability/10-static-html-render/src/App.tsx index 3389c4bf47..f1f4eb4d42 100644 --- a/examples/05-interoperability/10-static-html-render/src/App.tsx +++ b/examples/05-interoperability/10-static-html-render/src/App.tsx @@ -1,7 +1,6 @@ import "@blocknote/core/fonts/inter.css"; import "@blocknote/mantine/style.css"; import { useCreateBlockNote, usePrefersColorScheme } from "@blocknote/react"; -import { useRef, useEffect } from "react"; export default function App() { // Creates a new editor instance. @@ -87,9 +86,8 @@ export default function App() { { type: "image", props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { @@ -146,9 +144,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); @@ -159,7 +154,7 @@ export default function App() { // Renders the exported static HTML from the editor. return (
      diff --git a/examples/05-interoperability/10-static-html-render/tsconfig.json b/examples/05-interoperability/10-static-html-render/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/05-interoperability/10-static-html-render/tsconfig.json +++ b/examples/05-interoperability/10-static-html-render/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/05-interoperability/10-static-html-render/vite.config.ts +++ b/examples/05-interoperability/10-static-html-render/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/01-alert-block/.bnexample.json b/examples/06-custom-schema/01-alert-block/.bnexample.json index 955f240be5..1354e61187 100644 --- a/examples/06-custom-schema/01-alert-block/.bnexample.json +++ b/examples/06-custom-schema/01-alert-block/.bnexample.json @@ -10,7 +10,7 @@ "Slash Menu" ], "dependencies": { - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "react-icons": "^5.5.0" } } diff --git a/examples/06-custom-schema/01-alert-block/main.tsx b/examples/06-custom-schema/01-alert-block/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/01-alert-block/main.tsx +++ b/examples/06-custom-schema/01-alert-block/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/01-alert-block/package.json b/examples/06-custom-schema/01-alert-block/package.json index b02510a70f..acfec97721 100644 --- a/examples/06-custom-schema/01-alert-block/package.json +++ b/examples/06-custom-schema/01-alert-block/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/01-alert-block/src/App.tsx b/examples/06-custom-schema/01-alert-block/src/App.tsx index 929f5b8459..34a3afba7b 100644 --- a/examples/06-custom-schema/01-alert-block/src/App.tsx +++ b/examples/06-custom-schema/01-alert-block/src/App.tsx @@ -32,9 +32,6 @@ export default function App() { type: "paragraph", content: "Click the '!' icon to change the alert type", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/01-alert-block/tsconfig.json b/examples/06-custom-schema/01-alert-block/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/01-alert-block/tsconfig.json +++ b/examples/06-custom-schema/01-alert-block/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/06-custom-schema/01-alert-block/vite.config.ts +++ b/examples/06-custom-schema/01-alert-block/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/main.tsx b/examples/06-custom-schema/02-suggestion-menus-mentions/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/main.tsx +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json index ea836ebabe..72be5bca1f 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx b/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx index 4339153441..59efcc45f2 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx @@ -1,7 +1,4 @@ -import { - BlockNoteSchema, - defaultInlineContentSpecs, -} from "@blocknote/core"; +import { BlockNoteSchema, defaultInlineContentSpecs } from "@blocknote/core"; import { filterSuggestionItems } from "@blocknote/core/extensions"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; @@ -75,9 +72,6 @@ export function App() { type: "paragraph", content: "Press the '@' key to open the mentions menu and add another", }, - { - type: "paragraph", - }, ], }); 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/tsconfig.json +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/03-font-style/main.tsx b/examples/06-custom-schema/03-font-style/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/03-font-style/main.tsx +++ b/examples/06-custom-schema/03-font-style/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/03-font-style/package.json b/examples/06-custom-schema/03-font-style/package.json index ab031bf380..378c5d2d5f 100644 --- a/examples/06-custom-schema/03-font-style/package.json +++ b/examples/06-custom-schema/03-font-style/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/03-font-style/src/App.tsx b/examples/06-custom-schema/03-font-style/src/App.tsx index 4cae9935b1..1517696b83 100644 --- a/examples/06-custom-schema/03-font-style/src/App.tsx +++ b/examples/06-custom-schema/03-font-style/src/App.tsx @@ -60,6 +60,36 @@ const SetFontStyleButton = () => { ); }; +const CustomFormattingToolbar = () => ( + + + + + + + + + + + {/* Adds SetFontStyleButton */} + + + + + + + + + + + + + +); + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -91,9 +121,6 @@ export default function App() { content: "Highlight some text to open the Formatting Toolbar and change the font elsewhere", }, - { - type: "paragraph", - }, ], }); @@ -101,53 +128,7 @@ export default function App() { {/* Replaces the default Formatting Toolbar. */} ( - - - - - - - - - - - {/* Adds SetFontStyleButton */} - - - - - - - - - - - - - - )} + formattingToolbar={CustomFormattingToolbar} /> ); diff --git a/examples/06-custom-schema/03-font-style/tsconfig.json b/examples/06-custom-schema/03-font-style/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/03-font-style/tsconfig.json +++ b/examples/06-custom-schema/03-font-style/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/06-custom-schema/03-font-style/vite.config.ts +++ b/examples/06-custom-schema/03-font-style/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/04-pdf-file-block/.bnexample.json b/examples/06-custom-schema/04-pdf-file-block/.bnexample.json index f3f9f4d51b..c9ed35fa87 100644 --- a/examples/06-custom-schema/04-pdf-file-block/.bnexample.json +++ b/examples/06-custom-schema/04-pdf-file-block/.bnexample.json @@ -10,7 +10,7 @@ "Slash Menu" ], "dependencies": { - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "react-icons": "^5.5.0" }, "pro": true diff --git a/examples/06-custom-schema/04-pdf-file-block/main.tsx b/examples/06-custom-schema/04-pdf-file-block/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/04-pdf-file-block/main.tsx +++ b/examples/06-custom-schema/04-pdf-file-block/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/04-pdf-file-block/package.json b/examples/06-custom-schema/04-pdf-file-block/package.json index d5f5615cd0..5365379843 100644 --- a/examples/06-custom-schema/04-pdf-file-block/package.json +++ b/examples/06-custom-schema/04-pdf-file-block/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/04-pdf-file-block/src/App.tsx b/examples/06-custom-schema/04-pdf-file-block/src/App.tsx index 3c244a2840..c5a69d9b4b 100644 --- a/examples/06-custom-schema/04-pdf-file-block/src/App.tsx +++ b/examples/06-custom-schema/04-pdf-file-block/src/App.tsx @@ -67,9 +67,6 @@ export default function App() { type: "paragraph", content: "Press the '/' key to open the Slash Menu and add another PDF", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx index f17619d8ba..8ce605ea70 100644 --- a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx +++ b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx @@ -10,14 +10,7 @@ import { RiFilePdfFill } from "react-icons/ri"; import "./styles.css"; export const PDFPreview = ( - props: Omit< - ReactCustomBlockRenderProps< - FileBlockConfig["type"], - FileBlockConfig["propSchema"], - FileBlockConfig["content"] - >, - "contentRef" - >, + props: Omit, "contentRef">, ) => { return ( ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/05-alert-block-full-ux/.bnexample.json b/examples/06-custom-schema/05-alert-block-full-ux/.bnexample.json index 3326066fe0..7e53bcb415 100644 --- a/examples/06-custom-schema/05-alert-block-full-ux/.bnexample.json +++ b/examples/06-custom-schema/05-alert-block-full-ux/.bnexample.json @@ -11,7 +11,7 @@ "Slash Menu" ], "dependencies": { - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "react-icons": "^5.5.0" } } diff --git a/examples/06-custom-schema/05-alert-block-full-ux/main.tsx b/examples/06-custom-schema/05-alert-block-full-ux/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/05-alert-block-full-ux/main.tsx +++ b/examples/06-custom-schema/05-alert-block-full-ux/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/05-alert-block-full-ux/package.json b/examples/06-custom-schema/05-alert-block-full-ux/package.json index 55dc8b0bf1..aec27332bd 100644 --- a/examples/06-custom-schema/05-alert-block-full-ux/package.json +++ b/examples/06-custom-schema/05-alert-block-full-ux/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/05-alert-block-full-ux/src/App.tsx b/examples/06-custom-schema/05-alert-block-full-ux/src/App.tsx index 625dcce896..c8b93b6a25 100644 --- a/examples/06-custom-schema/05-alert-block-full-ux/src/App.tsx +++ b/examples/06-custom-schema/05-alert-block-full-ux/src/App.tsx @@ -13,6 +13,7 @@ import { SuggestionMenuController, blockTypeSelectItems, getDefaultReactSlashMenuItems, + useBlockNoteEditor, useCreateBlockNote, } from "@blocknote/react"; @@ -28,6 +29,31 @@ const schema = BlockNoteSchema.create().extend({ }, }); +const CustomFormattingToolbar = () => { + const editor = useBlockNoteEditor< + typeof schema.blockSchema, + typeof schema.inlineContentSchema, + typeof schema.styleSchema + >(); + + return ( + // Uses the default Formatting Toolbar. + + ); +}; + // Slash menu item to insert an Alert block const insertAlert = (editor: typeof schema.BlockNoteEditor) => ({ title: "Alert", @@ -75,9 +101,6 @@ export default function App() { content: "Or select some text to see the alert in the Formatting Toolbar's Block Type Select", }, - { - type: "paragraph", - }, ], }); @@ -86,22 +109,7 @@ export default function App() { {/* Replaces the default Formatting Toolbar */} ( - // Uses the default Formatting Toolbar. - - )} + formattingToolbar={CustomFormattingToolbar} /> {/* Replaces the default Slash Menu. */} ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/06-toggleable-blocks/main.tsx b/examples/06-custom-schema/06-toggleable-blocks/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/main.tsx +++ b/examples/06-custom-schema/06-toggleable-blocks/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/06-toggleable-blocks/package.json b/examples/06-custom-schema/06-toggleable-blocks/package.json index 5449c535a2..b82964999e 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/package.json +++ b/examples/06-custom-schema/06-toggleable-blocks/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/06-toggleable-blocks/src/App.tsx b/examples/06-custom-schema/06-toggleable-blocks/src/App.tsx index b1833d5f27..81877de7e4 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/src/App.tsx +++ b/examples/06-custom-schema/06-toggleable-blocks/src/App.tsx @@ -47,9 +47,6 @@ export default function App() { type: "paragraph", content: "Click the '>' icon to show/hide its children", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json b/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json +++ b/examples/06-custom-schema/06-toggleable-blocks/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/vite.config.ts +++ b/examples/06-custom-schema/06-toggleable-blocks/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/07-configuring-blocks/main.tsx b/examples/06-custom-schema/07-configuring-blocks/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/07-configuring-blocks/main.tsx +++ b/examples/06-custom-schema/07-configuring-blocks/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/07-configuring-blocks/package.json b/examples/06-custom-schema/07-configuring-blocks/package.json index edf107c487..aa9f54843e 100644 --- a/examples/06-custom-schema/07-configuring-blocks/package.json +++ b/examples/06-custom-schema/07-configuring-blocks/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/07-configuring-blocks/src/App.tsx b/examples/06-custom-schema/07-configuring-blocks/src/App.tsx index 491dc8a1c1..47db15f007 100644 --- a/examples/06-custom-schema/07-configuring-blocks/src/App.tsx +++ b/examples/06-custom-schema/07-configuring-blocks/src/App.tsx @@ -32,9 +32,6 @@ export default function App() { content: "Notice how only heading levels 1-3 are available, and toggle headings are not shown.", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/07-configuring-blocks/tsconfig.json b/examples/06-custom-schema/07-configuring-blocks/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/07-configuring-blocks/tsconfig.json +++ b/examples/06-custom-schema/07-configuring-blocks/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/06-custom-schema/07-configuring-blocks/vite.config.ts +++ b/examples/06-custom-schema/07-configuring-blocks/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/08-non-editable-block/.bnexample.json b/examples/06-custom-schema/08-non-editable-block/.bnexample.json new file mode 100644 index 0000000000..c94d1e9154 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": ["Intermediate", "Blocks", "Custom Schemas"] +} diff --git a/examples/06-custom-schema/08-non-editable-block/README.md b/examples/06-custom-schema/08-non-editable-block/README.md new file mode 100644 index 0000000000..9c7cce19d1 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/README.md @@ -0,0 +1,8 @@ +# Non-Editable Block + +In this example, we create a custom block which renders a simple HTML paragraph with placeholder text. The block has no editable content. + +**Relevant Docs:** + +- [Custom Blocks](/docs/features/custom-schemas/custom-blocks) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/06-custom-schema/08-non-editable-block/index.html b/examples/06-custom-schema/08-non-editable-block/index.html new file mode 100644 index 0000000000..9b55422066 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/index.html @@ -0,0 +1,14 @@ + + + + + Non-Editable Block + + + +
      + + + diff --git a/examples/06-custom-schema/08-non-editable-block/main.tsx b/examples/06-custom-schema/08-non-editable-block/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-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/08-non-editable-block/package.json b/examples/06-custom-schema/08-non-editable-block/package.json new file mode 100644 index 0000000000..3d7e74b8c1 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-custom-schema-non-editable-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp 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" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/06-custom-schema/08-non-editable-block/src/App.tsx b/examples/06-custom-schema/08-non-editable-block/src/App.tsx new file mode 100644 index 0000000000..ca7a4cd8d0 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/src/App.tsx @@ -0,0 +1,35 @@ +import { BlockNoteSchema } from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +import { createNonEditableBlock } from "./NonEditableBlock"; + +// 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 Non-Editable block and adds it to the schema. + nonEditable: createNonEditableBlock(), + }, +}); + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "nonEditable", + }, + ], + }); + + // Renders the editor instance. + return ; +} diff --git a/examples/06-custom-schema/08-non-editable-block/src/NonEditableBlock.tsx b/examples/06-custom-schema/08-non-editable-block/src/NonEditableBlock.tsx new file mode 100644 index 0000000000..a930c21f74 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/src/NonEditableBlock.tsx @@ -0,0 +1,13 @@ +import { createReactBlockSpec } from "@blocknote/react"; + +// The Non-Editable block. +export const createNonEditableBlock = createReactBlockSpec( + { + type: "nonEditable", + propSchema: {}, + content: "none", + }, + { + render: () =>

      This is a non-editable block.

      , + }, +); diff --git a/examples/06-custom-schema/08-non-editable-block/tsconfig.json b/examples/06-custom-schema/08-non-editable-block/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__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 + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} 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 new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/vite.config.ts @@ -0,0 +1,31 @@ +// 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-plus"; +// 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")) + ? {} + : ({ + // 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/main.tsx b/examples/06-custom-schema/draggable-inline-content/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/draggable-inline-content/main.tsx +++ b/examples/06-custom-schema/draggable-inline-content/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/draggable-inline-content/package.json b/examples/06-custom-schema/draggable-inline-content/package.json index 98db481029..036466ee5d 100644 --- a/examples/06-custom-schema/draggable-inline-content/package.json +++ b/examples/06-custom-schema/draggable-inline-content/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/draggable-inline-content/tsconfig.json b/examples/06-custom-schema/draggable-inline-content/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/draggable-inline-content/tsconfig.json +++ b/examples/06-custom-schema/draggable-inline-content/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/06-custom-schema/draggable-inline-content/vite.config.ts +++ b/examples/06-custom-schema/draggable-inline-content/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/react-custom-blocks/main.tsx b/examples/06-custom-schema/react-custom-blocks/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/react-custom-blocks/main.tsx +++ b/examples/06-custom-schema/react-custom-blocks/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/react-custom-blocks/package.json b/examples/06-custom-schema/react-custom-blocks/package.json index 0372b1c809..059a4e960b 100644 --- a/examples/06-custom-schema/react-custom-blocks/package.json +++ b/examples/06-custom-schema/react-custom-blocks/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/react-custom-blocks/src/App.tsx b/examples/06-custom-schema/react-custom-blocks/src/App.tsx index dd0573877d..15ad3ebd36 100644 --- a/examples/06-custom-schema/react-custom-blocks/src/App.tsx +++ b/examples/06-custom-schema/react-custom-blocks/src/App.tsx @@ -81,8 +81,7 @@ const simpleImageBlock = createReactBlockSpec( type: "simpleImage", propSchema: { src: { - default: - "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + default: "https://placehold.co/800x540.png", }, }, content: "none", @@ -142,7 +141,7 @@ export default function App() { { type: "simpleImage", props: { - src: "https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg", + src: "https://placehold.co/332x322.jpg", }, }, { diff --git a/examples/06-custom-schema/react-custom-blocks/tsconfig.json b/examples/06-custom-schema/react-custom-blocks/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/react-custom-blocks/tsconfig.json +++ b/examples/06-custom-schema/react-custom-blocks/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/06-custom-schema/react-custom-blocks/vite.config.ts +++ b/examples/06-custom-schema/react-custom-blocks/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/react-custom-inline-content/main.tsx b/examples/06-custom-schema/react-custom-inline-content/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/react-custom-inline-content/main.tsx +++ b/examples/06-custom-schema/react-custom-inline-content/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/react-custom-inline-content/package.json b/examples/06-custom-schema/react-custom-inline-content/package.json index 98abca8461..e42b345f5f 100644 --- a/examples/06-custom-schema/react-custom-inline-content/package.json +++ b/examples/06-custom-schema/react-custom-inline-content/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/react-custom-inline-content/tsconfig.json +++ b/examples/06-custom-schema/react-custom-inline-content/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/06-custom-schema/react-custom-styles/main.tsx b/examples/06-custom-schema/react-custom-styles/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/06-custom-schema/react-custom-styles/main.tsx +++ b/examples/06-custom-schema/react-custom-styles/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/06-custom-schema/react-custom-styles/package.json b/examples/06-custom-schema/react-custom-styles/package.json index eb88b335ff..6030defacc 100644 --- a/examples/06-custom-schema/react-custom-styles/package.json +++ b/examples/06-custom-schema/react-custom-styles/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/06-custom-schema/react-custom-styles/tsconfig.json b/examples/06-custom-schema/react-custom-styles/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/06-custom-schema/react-custom-styles/tsconfig.json +++ b/examples/06-custom-schema/react-custom-styles/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/06-custom-schema/react-custom-styles/vite.config.ts +++ b/examples/06-custom-schema/react-custom-styles/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/01-partykit/main.tsx b/examples/07-collaboration/01-partykit/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/01-partykit/main.tsx +++ b/examples/07-collaboration/01-partykit/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/01-partykit/package.json b/examples/07-collaboration/01-partykit/package.json index c29a4981a9..273a41c355 100644 --- a/examples/07-collaboration/01-partykit/package.json +++ b/examples/07-collaboration/01-partykit/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/01-partykit/tsconfig.json b/examples/07-collaboration/01-partykit/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/01-partykit/tsconfig.json +++ b/examples/07-collaboration/01-partykit/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/07-collaboration/01-partykit/vite.config.ts b/examples/07-collaboration/01-partykit/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/01-partykit/vite.config.ts +++ b/examples/07-collaboration/01-partykit/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/02-liveblocks/.bnexample.json b/examples/07-collaboration/02-liveblocks/.bnexample.json index b212ead624..f74c6d26bb 100644 --- a/examples/07-collaboration/02-liveblocks/.bnexample.json +++ b/examples/07-collaboration/02-liveblocks/.bnexample.json @@ -4,11 +4,11 @@ "author": "yousefed", "tags": ["Advanced", "Saving/Loading", "Collaboration"], "dependencies": { - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", + "@liveblocks/client": "^3.17.0", + "@liveblocks/react": "^3.17.0", + "@liveblocks/react-blocknote": "^3.17.0", + "@liveblocks/react-tiptap": "^3.17.0", + "@liveblocks/react-ui": "^3.17.0", "yjs": "^13.6.27" } } diff --git a/examples/07-collaboration/02-liveblocks/main.tsx b/examples/07-collaboration/02-liveblocks/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/02-liveblocks/main.tsx +++ b/examples/07-collaboration/02-liveblocks/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index 42dac31b38..d266439d14 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,22 +16,21 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", + "@liveblocks/client": "^3.17.0", + "@liveblocks/react": "^3.17.0", + "@liveblocks/react-blocknote": "^3.17.0", + "@liveblocks/react-tiptap": "^3.17.0", + "@liveblocks/react-ui": "^3.17.0", "yjs": "^13.6.27" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/02-liveblocks/tsconfig.json b/examples/07-collaboration/02-liveblocks/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/02-liveblocks/tsconfig.json +++ b/examples/07-collaboration/02-liveblocks/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/07-collaboration/02-liveblocks/vite.config.ts b/examples/07-collaboration/02-liveblocks/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/02-liveblocks/vite.config.ts +++ b/examples/07-collaboration/02-liveblocks/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/03-y-sweet/main.tsx b/examples/07-collaboration/03-y-sweet/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/03-y-sweet/main.tsx +++ b/examples/07-collaboration/03-y-sweet/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/03-y-sweet/package.json b/examples/07-collaboration/03-y-sweet/package.json index ca2e4b0097..9dc5563015 100644 --- a/examples/07-collaboration/03-y-sweet/package.json +++ b/examples/07-collaboration/03-y-sweet/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@y-sweet/react": "^0.6.3" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/03-y-sweet/tsconfig.json b/examples/07-collaboration/03-y-sweet/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/03-y-sweet/tsconfig.json +++ b/examples/07-collaboration/03-y-sweet/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/07-collaboration/03-y-sweet/vite.config.ts b/examples/07-collaboration/03-y-sweet/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/03-y-sweet/vite.config.ts +++ b/examples/07-collaboration/03-y-sweet/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/04-electric-sql/main.tsx b/examples/07-collaboration/04-electric-sql/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/04-electric-sql/main.tsx +++ b/examples/07-collaboration/04-electric-sql/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/04-electric-sql/package.json b/examples/07-collaboration/04-electric-sql/package.json index dd9d538875..ec54798c99 100644 --- a/examples/07-collaboration/04-electric-sql/package.json +++ b/examples/07-collaboration/04-electric-sql/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/04-electric-sql/tsconfig.json b/examples/07-collaboration/04-electric-sql/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/04-electric-sql/tsconfig.json +++ b/examples/07-collaboration/04-electric-sql/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/07-collaboration/04-electric-sql/vite.config.ts b/examples/07-collaboration/04-electric-sql/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/04-electric-sql/vite.config.ts +++ b/examples/07-collaboration/04-electric-sql/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/05-comments/.bnexample.json b/examples/07-collaboration/05-comments/.bnexample.json index 09ecc7196c..f2e0c026a9 100644 --- a/examples/07-collaboration/05-comments/.bnexample.json +++ b/examples/07-collaboration/05-comments/.bnexample.json @@ -5,6 +5,6 @@ "tags": ["Advanced", "Comments", "Collaboration"], "dependencies": { "@y-sweet/react": "^0.6.3", - "@mantine/core": "^8.3.11" + "@mantine/core": "^9.0.2" } } diff --git a/examples/07-collaboration/05-comments/main.tsx b/examples/07-collaboration/05-comments/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/05-comments/main.tsx +++ b/examples/07-collaboration/05-comments/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/05-comments/package.json b/examples/07-collaboration/05-comments/package.json index 27897406ce..9d49622dda 100644 --- a/examples/07-collaboration/05-comments/package.json +++ b/examples/07-collaboration/05-comments/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@y-sweet/react": "^0.6.3" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/05-comments/tsconfig.json b/examples/07-collaboration/05-comments/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/05-comments/tsconfig.json +++ b/examples/07-collaboration/05-comments/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/07-collaboration/05-comments/vite.config.ts b/examples/07-collaboration/05-comments/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/05-comments/vite.config.ts +++ b/examples/07-collaboration/05-comments/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json b/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json index a80bff5aba..ff82fe290f 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json +++ b/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json @@ -6,6 +6,6 @@ "dependencies": { "y-partykit": "^0.0.25", "yjs": "^13.6.27", - "@mantine/core": "^8.3.11" + "@mantine/core": "^9.0.2" } } diff --git a/examples/07-collaboration/06-comments-with-sidebar/main.tsx b/examples/07-collaboration/06-comments-with-sidebar/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/main.tsx +++ b/examples/07-collaboration/06-comments-with-sidebar/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/06-comments-with-sidebar/package.json b/examples/07-collaboration/06-comments-with-sidebar/package.json index c1a6c49f9a..ed49891bf0 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/package.json +++ b/examples/07-collaboration/06-comments-with-sidebar/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/06-comments-with-sidebar/src/App.tsx b/examples/07-collaboration/06-comments-with-sidebar/src/App.tsx index 84ad0d577a..3c2ea24638 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/src/App.tsx +++ b/examples/07-collaboration/06-comments-with-sidebar/src/App.tsx @@ -73,7 +73,7 @@ export default function App() { doc.getMap("threads"), new DefaultThreadStoreAuth(activeUser.id, activeUser.role), ); - }, [doc, activeUser]); + }, [activeUser]); // setup the editor with comments and collaboration const editor = useCreateBlockNote( diff --git a/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json b/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json +++ b/examples/07-collaboration/06-comments-with-sidebar/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/vite.config.ts +++ b/examples/07-collaboration/06-comments-with-sidebar/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/07-ghost-writer/main.tsx b/examples/07-collaboration/07-ghost-writer/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/07-ghost-writer/main.tsx +++ b/examples/07-collaboration/07-ghost-writer/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/07-ghost-writer/package.json b/examples/07-collaboration/07-ghost-writer/package.json index a45bc3a305..daafe80e80 100644 --- a/examples/07-collaboration/07-ghost-writer/package.json +++ b/examples/07-collaboration/07-ghost-writer/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/07-ghost-writer/src/App.tsx b/examples/07-collaboration/07-ghost-writer/src/App.tsx index 4344c5c11a..5d2c39db79 100644 --- a/examples/07-collaboration/07-ghost-writer/src/App.tsx +++ b/examples/07-collaboration/07-ghost-writer/src/App.tsx @@ -7,7 +7,7 @@ import YPartyKitProvider from "y-partykit/provider"; import * as Y from "yjs"; import "./styles.css"; import { useEffect, useState } from "react"; -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line import-eslint/no-extraneous-dependencies import { EditorView } from "prosemirror-view"; const params = new URLSearchParams(window.location.search); @@ -101,7 +101,8 @@ export default function App() { `${window.location.origin}${window.location.pathname}?room=${roomName}&index=-1`, "_blank", ); - }}> + }} + > Ghost Writer in a new window diff --git a/examples/07-collaboration/07-ghost-writer/tsconfig.json b/examples/07-collaboration/07-ghost-writer/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/07-ghost-writer/tsconfig.json +++ b/examples/07-collaboration/07-ghost-writer/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/07-collaboration/07-ghost-writer/vite.config.ts b/examples/07-collaboration/07-ghost-writer/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/07-ghost-writer/vite.config.ts +++ b/examples/07-collaboration/07-ghost-writer/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/08-forking/main.tsx b/examples/07-collaboration/08-forking/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/07-collaboration/08-forking/main.tsx +++ b/examples/07-collaboration/08-forking/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/07-collaboration/08-forking/package.json b/examples/07-collaboration/08-forking/package.json index 08ca3f389d..9ba9477066 100644 --- a/examples/07-collaboration/08-forking/package.json +++ b/examples/07-collaboration/08-forking/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/07-collaboration/08-forking/tsconfig.json b/examples/07-collaboration/08-forking/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/07-collaboration/08-forking/tsconfig.json +++ b/examples/07-collaboration/08-forking/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/07-collaboration/08-forking/vite.config.ts b/examples/07-collaboration/08-forking/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/07-collaboration/08-forking/vite.config.ts +++ b/examples/07-collaboration/08-forking/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/07-collaboration/09-comments-testing/.bnexample.json b/examples/07-collaboration/09-comments-testing/.bnexample.json new file mode 100644 index 0000000000..5d7d986420 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": ["Advanced", "Comments", "Testing"], + "dependencies": { + "yjs": "^13.6.27" + } +} diff --git a/examples/07-collaboration/09-comments-testing/README.md b/examples/07-collaboration/09-comments-testing/README.md new file mode 100644 index 0000000000..b59f2ecd1b --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/README.md @@ -0,0 +1,3 @@ +# Comments Testing + +A minimal comments example used for end-to-end testing. Uses a local Y.Doc (no collaboration provider) with a single hardcoded editor user. diff --git a/examples/07-collaboration/09-comments-testing/index.html b/examples/07-collaboration/09-comments-testing/index.html new file mode 100644 index 0000000000..f50976be79 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/index.html @@ -0,0 +1,14 @@ + + + + + Comments Testing + + + +
      + + + diff --git a/examples/07-collaboration/09-comments-testing/main.tsx b/examples/07-collaboration/09-comments-testing/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/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/07-collaboration/09-comments-testing/package.json b/examples/07-collaboration/09-comments-testing/package.json new file mode 100644 index 0000000000..6127677588 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-collaboration-comments-testing", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp 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", + "yjs": "^13.6.27" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/07-collaboration/09-comments-testing/src/App.tsx b/examples/07-collaboration/09-comments-testing/src/App.tsx new file mode 100644 index 0000000000..3bada358c1 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/src/App.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { + CommentsExtension, + DefaultThreadStoreAuth, + YjsThreadStore, +} from "@blocknote/core/comments"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { useMemo } from "react"; +import * as Y from "yjs"; + +const USER = { + id: "1", + username: "John Doe", + avatarUrl: "https://placehold.co/100x100?text=John", + role: "editor" as const, +}; + +async function resolveUsers(userIds: string[]) { + return [USER].filter((user) => userIds.includes(user.id)); +} + +export default function App() { + const doc = useMemo(() => new Y.Doc(), []); + + const threadStore = useMemo(() => { + return new YjsThreadStore( + USER.id, + doc.getMap("threads"), + new DefaultThreadStoreAuth(USER.id, USER.role), + ); + }, [doc]); + + const editor = useCreateBlockNote( + { + extensions: [CommentsExtension({ threadStore, resolveUsers })], + }, + [threadStore], + ); + + return ; +} diff --git a/examples/07-collaboration/09-comments-testing/tsconfig.json b/examples/07-collaboration/09-comments-testing/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__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 + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/07-collaboration/09-comments-testing/vite.config.ts b/examples/07-collaboration/09-comments-testing/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/vite.config.ts @@ -0,0 +1,31 @@ +// 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-plus"; +// 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")) + ? {} + : ({ + // 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/08-extensions/01-tiptap-arrow-conversion/main.tsx b/examples/08-extensions/01-tiptap-arrow-conversion/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/main.tsx +++ b/examples/08-extensions/01-tiptap-arrow-conversion/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/package.json b/examples/08-extensions/01-tiptap-arrow-conversion/package.json index 7eaeaf3eaa..84bd386a13 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/package.json +++ b/examples/08-extensions/01-tiptap-arrow-conversion/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@tiptap/core": "^3.13.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json b/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json +++ b/examples/08-extensions/01-tiptap-arrow-conversion/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/vite.config.ts +++ b/examples/08-extensions/01-tiptap-arrow-conversion/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/09-ai/01-minimal/.bnexample.json b/examples/09-ai/01-minimal/.bnexample.json index 30d6def791..9aede450f7 100644 --- a/examples/09-ai/01-minimal/.bnexample.json +++ b/examples/09-ai/01-minimal/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/01-minimal/main.tsx b/examples/09-ai/01-minimal/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/09-ai/01-minimal/main.tsx +++ b/examples/09-ai/01-minimal/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/09-ai/01-minimal/package.json b/examples/09-ai/01-minimal/package.json index b244123bd7..9ffd51ef4e 100644 --- a/examples/09-ai/01-minimal/package.json +++ b/examples/09-ai/01-minimal/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/09-ai/01-minimal/src/App.tsx b/examples/09-ai/01-minimal/src/App.tsx index 37417d292b..f83d49b1a6 100644 --- a/examples/09-ai/01-minimal/src/App.tsx +++ b/examples/09-ai/01-minimal/src/App.tsx @@ -22,12 +22,27 @@ import { en as aiEn } from "@blocknote/xl-ai/locales"; import "@blocknote/xl-ai/style.css"; import { DefaultChatTransport } from "ai"; -import { useEffect } from "react"; import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -84,54 +99,25 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + - {/* We disabled the default SlashMenu with `slashMenu=false` - and replace it for one with an AI option (defined below). + {/* We disabled the default SlashMenu with `slashMenu=false` + and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
      ); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/01-minimal/tsconfig.json b/examples/09-ai/01-minimal/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/09-ai/01-minimal/tsconfig.json +++ b/examples/09-ai/01-minimal/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/09-ai/01-minimal/vite.config.ts b/examples/09-ai/01-minimal/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/09-ai/01-minimal/vite.config.ts +++ b/examples/09-ai/01-minimal/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/09-ai/02-playground/.bnexample.json b/examples/09-ai/02-playground/.bnexample.json index 30d6def791..9aede450f7 100644 --- a/examples/09-ai/02-playground/.bnexample.json +++ b/examples/09-ai/02-playground/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/02-playground/main.tsx b/examples/09-ai/02-playground/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/09-ai/02-playground/main.tsx +++ b/examples/09-ai/02-playground/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/09-ai/02-playground/package.json b/examples/09-ai/02-playground/package.json index 9bfc984a4a..ed87776b02 100644 --- a/examples/09-ai/02-playground/package.json +++ b/examples/09-ai/02-playground/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/09-ai/02-playground/src/App.tsx b/examples/09-ai/02-playground/src/App.tsx index ace8b47d79..af98df17c9 100644 --- a/examples/09-ai/02-playground/src/App.tsx +++ b/examples/09-ai/02-playground/src/App.tsx @@ -33,6 +33,20 @@ import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + ...getAISlashMenuItems(editor), +]; + export default function App() { const [model, setModel] = useState( "groq.chat/llama-3.3-70b-versatile", @@ -129,52 +143,25 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + - {/* We disabled the default SlashMenu with `slashMenu=false` + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
      ); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/02-playground/tsconfig.json b/examples/09-ai/02-playground/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/09-ai/02-playground/tsconfig.json +++ b/examples/09-ai/02-playground/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/09-ai/02-playground/vite.config.ts b/examples/09-ai/02-playground/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/09-ai/02-playground/vite.config.ts +++ b/examples/09-ai/02-playground/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/09-ai/03-custom-ai-menu-items/.bnexample.json b/examples/09-ai/03-custom-ai-menu-items/.bnexample.json index a026ef1ee5..9a91d82062 100644 --- a/examples/09-ai/03-custom-ai-menu-items/.bnexample.json +++ b/examples/09-ai/03-custom-ai-menu-items/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5", "react-icons": "^5.5.0" } diff --git a/examples/09-ai/03-custom-ai-menu-items/main.tsx b/examples/09-ai/03-custom-ai-menu-items/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/09-ai/03-custom-ai-menu-items/main.tsx +++ b/examples/09-ai/03-custom-ai-menu-items/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/09-ai/03-custom-ai-menu-items/package.json b/examples/09-ai/03-custom-ai-menu-items/package.json index 1385ceab9b..d19ed17584 100644 --- a/examples/09-ai/03-custom-ai-menu-items/package.json +++ b/examples/09-ai/03-custom-ai-menu-items/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -28,7 +27,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/09-ai/03-custom-ai-menu-items/src/App.tsx b/examples/09-ai/03-custom-ai-menu-items/src/App.tsx index 8eee964b9f..be01e2d75a 100644 --- a/examples/09-ai/03-custom-ai-menu-items/src/App.tsx +++ b/examples/09-ai/03-custom-ai-menu-items/src/App.tsx @@ -30,6 +30,63 @@ import { addRelatedTopics, makeInformal } from "./customAIMenuItems"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +function CustomAIMenu() { + return ( + , + aiResponseStatus: + | "user-input" + | "thinking" + | "ai-writing" + | "error" + | "user-reviewing" + | "closed", + ) => { + if (aiResponseStatus === "user-input") { + // Returns different items based on whether the AI Menu was + // opened via the Formatting Toolbar or the Slash Menu. + if (editor.getSelection()) { + return [ + // Gets the default AI Menu items + ...getDefaultAIMenuItems(editor, aiResponseStatus), + // Adds our custom item to make the text more casual. + // Only appears when the AI Menu is opened via the + // Formatting Toolbar. + makeInformal(editor), + ]; + } else { + return [ + // Gets the default AI Menu items + ...getDefaultAIMenuItems(editor, aiResponseStatus), + // Adds our custom item to find related topics. Only + // appears when the AI Menu is opened via the Slash + // Menu. + addRelatedTopics(editor), + ]; + } + } + // for other states, return the default items + return getDefaultAIMenuItems(editor, aiResponseStatus); + }} + /> + ); +} + +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + ...getAISlashMenuItems(editor), +]; + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -81,99 +138,29 @@ export default function App() { slashMenu={false} style={{ paddingBottom: "300px" }} > - {/* Creates a new AIMenu with the default items, + {/* Creates a new AIMenu with the default items, as well as our custom ones. */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + - {/* We disabled the default SlashMenu with `slashMenu=false` + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
      ); } - -function CustomAIMenu() { - return ( - , - aiResponseStatus: - | "user-input" - | "thinking" - | "ai-writing" - | "error" - | "user-reviewing" - | "closed", - ) => { - if (aiResponseStatus === "user-input") { - // Returns different items based on whether the AI Menu was - // opened via the Formatting Toolbar or the Slash Menu. - if (editor.getSelection()) { - return [ - // Gets the default AI Menu items - ...getDefaultAIMenuItems(editor, aiResponseStatus), - // Adds our custom item to make the text more casual. - // Only appears when the AI Menu is opened via the - // Formatting Toolbar. - makeInformal(editor), - ]; - } else { - return [ - // Gets the default AI Menu items - ...getDefaultAIMenuItems(editor, aiResponseStatus), - // Adds our custom item to find related topics. Only - // appears when the AI Menu is opened via the Slash - // Menu. - addRelatedTopics(editor), - ]; - } - } - // for other states, return the default items - return getDefaultAIMenuItems(editor, aiResponseStatus); - }} - /> - ); -} - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/09-ai/03-custom-ai-menu-items/tsconfig.json +++ b/examples/09-ai/03-custom-ai-menu-items/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/09-ai/04-with-collaboration/.bnexample.json b/examples/09-ai/04-with-collaboration/.bnexample.json index 922d7f719e..83bed82fe4 100644 --- a/examples/09-ai/04-with-collaboration/.bnexample.json +++ b/examples/09-ai/04-with-collaboration/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5", "y-partykit": "^0.0.25", "yjs": "^13.6.27" diff --git a/examples/09-ai/04-with-collaboration/main.tsx b/examples/09-ai/04-with-collaboration/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/09-ai/04-with-collaboration/main.tsx +++ b/examples/09-ai/04-with-collaboration/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/09-ai/04-with-collaboration/package.json b/examples/09-ai/04-with-collaboration/package.json index fe17faaa59..bf251e1ec0 100644 --- a/examples/09-ai/04-with-collaboration/package.json +++ b/examples/09-ai/04-with-collaboration/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -29,7 +28,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/09-ai/04-with-collaboration/src/App.tsx b/examples/09-ai/04-with-collaboration/src/App.tsx index 3fd8076ccd..7052a9145e 100644 --- a/examples/09-ai/04-with-collaboration/src/App.tsx +++ b/examples/09-ai/04-with-collaboration/src/App.tsx @@ -23,7 +23,7 @@ import "@blocknote/xl-ai/style.css"; import { useEffect, useState } from "react"; import YPartyKitProvider from "y-partykit/provider"; import * as Y from "yjs"; -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line import-eslint/no-extraneous-dependencies import { EditorView } from "prosemirror-view"; import { DefaultChatTransport } from "ai"; @@ -58,6 +58,22 @@ if (isGhostWriting) { const ghostContent = "This demo shows a two-way sync of documents. It allows you to test collaboration features, and see how stable the editor is. "; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + export default function App() { const [numGhostWriters, setNumGhostWriters] = useState(1); const [isPaused, setIsPaused] = useState(false); @@ -176,17 +192,24 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + - {/* We disabled the default SlashMenu with `slashMenu=false` + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + /> {!isGhostWriting && ( @@ -205,39 +228,3 @@ export default function App() { ); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/04-with-collaboration/tsconfig.json b/examples/09-ai/04-with-collaboration/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/09-ai/04-with-collaboration/tsconfig.json +++ b/examples/09-ai/04-with-collaboration/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/09-ai/04-with-collaboration/vite.config.ts b/examples/09-ai/04-with-collaboration/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/09-ai/04-with-collaboration/vite.config.ts +++ b/examples/09-ai/04-with-collaboration/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/09-ai/05-manual-execution/.bnexample.json b/examples/09-ai/05-manual-execution/.bnexample.json index c5b86534d1..890b2909fe 100644 --- a/examples/09-ai/05-manual-execution/.bnexample.json +++ b/examples/09-ai/05-manual-execution/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5", "y-partykit": "^0.0.25", "yjs": "^13.6.27" diff --git a/examples/09-ai/05-manual-execution/main.tsx b/examples/09-ai/05-manual-execution/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/09-ai/05-manual-execution/main.tsx +++ b/examples/09-ai/05-manual-execution/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/09-ai/05-manual-execution/package.json b/examples/09-ai/05-manual-execution/package.json index b23f7678a0..5335b5ae66 100644 --- a/examples/09-ai/05-manual-execution/package.json +++ b/examples/09-ai/05-manual-execution/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -29,7 +28,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/09-ai/05-manual-execution/tsconfig.json b/examples/09-ai/05-manual-execution/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/09-ai/05-manual-execution/tsconfig.json +++ b/examples/09-ai/05-manual-execution/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/09-ai/05-manual-execution/vite.config.ts b/examples/09-ai/05-manual-execution/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/09-ai/05-manual-execution/vite.config.ts +++ b/examples/09-ai/05-manual-execution/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/09-ai/06-client-side-transport/.bnexample.json b/examples/09-ai/06-client-side-transport/.bnexample.json index 1c7e871335..0ac2b679fc 100644 --- a/examples/09-ai/06-client-side-transport/.bnexample.json +++ b/examples/09-ai/06-client-side-transport/.bnexample.json @@ -6,7 +6,7 @@ "dependencies": { "@ai-sdk/groq": "^3.0.2", "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/06-client-side-transport/main.tsx b/examples/09-ai/06-client-side-transport/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/09-ai/06-client-side-transport/main.tsx +++ b/examples/09-ai/06-client-side-transport/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/09-ai/06-client-side-transport/package.json b/examples/09-ai/06-client-side-transport/package.json index 4a143fbef3..ce9940b348 100644 --- a/examples/09-ai/06-client-side-transport/package.json +++ b/examples/09-ai/06-client-side-transport/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@ai-sdk/groq": "^3.0.2", @@ -28,7 +27,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/09-ai/06-client-side-transport/src/App.tsx b/examples/09-ai/06-client-side-transport/src/App.tsx index ca7b64518e..dc42ad9dc2 100644 --- a/examples/09-ai/06-client-side-transport/src/App.tsx +++ b/examples/09-ai/06-client-side-transport/src/App.tsx @@ -28,6 +28,22 @@ import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + // We define the model directly in our app using the Vercel AI SDK const model = createGroq({ // We supply a custom fetch function so that requests are routed through our proxy server @@ -97,54 +113,25 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + - {/* We disabled the default SlashMenu with `slashMenu=false` + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
      ); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/06-client-side-transport/tsconfig.json b/examples/09-ai/06-client-side-transport/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/09-ai/06-client-side-transport/tsconfig.json +++ b/examples/09-ai/06-client-side-transport/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/09-ai/06-client-side-transport/vite.config.ts +++ b/examples/09-ai/06-client-side-transport/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/09-ai/07-server-persistence/.bnexample.json b/examples/09-ai/07-server-persistence/.bnexample.json index 35815d0d21..12b79358ef 100644 --- a/examples/09-ai/07-server-persistence/.bnexample.json +++ b/examples/09-ai/07-server-persistence/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/07-server-persistence/main.tsx b/examples/09-ai/07-server-persistence/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/09-ai/07-server-persistence/main.tsx +++ b/examples/09-ai/07-server-persistence/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/09-ai/07-server-persistence/package.json b/examples/09-ai/07-server-persistence/package.json index b379364817..92f432e7e3 100644 --- a/examples/09-ai/07-server-persistence/package.json +++ b/examples/09-ai/07-server-persistence/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/09-ai/07-server-persistence/src/App.tsx b/examples/09-ai/07-server-persistence/src/App.tsx index a22d9c9e1c..02c17cdb00 100644 --- a/examples/09-ai/07-server-persistence/src/App.tsx +++ b/examples/09-ai/07-server-persistence/src/App.tsx @@ -26,6 +26,22 @@ import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -112,54 +128,25 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + - {/* We disabled the default SlashMenu with `slashMenu=false` + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
      ); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/07-server-persistence/tsconfig.json b/examples/09-ai/07-server-persistence/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/09-ai/07-server-persistence/tsconfig.json +++ b/examples/09-ai/07-server-persistence/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/09-ai/07-server-persistence/vite.config.ts b/examples/09-ai/07-server-persistence/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/09-ai/07-server-persistence/vite.config.ts +++ b/examples/09-ai/07-server-persistence/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/main.tsx b/examples/vanilla-js/react-vanilla-custom-blocks/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/main.tsx +++ b/examples/vanilla-js/react-vanilla-custom-blocks/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/package.json b/examples/vanilla-js/react-vanilla-custom-blocks/package.json index caf42e11e5..e0fbb389f3 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/package.json +++ b/examples/vanilla-js/react-vanilla-custom-blocks/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx b/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx index 3386861a88..b0711912e3 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx +++ b/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx @@ -115,8 +115,7 @@ const simpleImageBlock = createBlockSpec( type: "simpleImage", propSchema: { src: { - default: - "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + default: "https://placehold.co/800x540.png", }, }, content: "none", @@ -201,7 +200,7 @@ export default function App() { { type: "simpleImage", props: { - src: "https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg", + src: "https://placehold.co/800x540.png", }, }, { diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json b/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json +++ b/examples/vanilla-js/react-vanilla-custom-blocks/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/vite.config.ts +++ b/examples/vanilla-js/react-vanilla-custom-blocks/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/main.tsx b/examples/vanilla-js/react-vanilla-custom-inline-content/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/main.tsx +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json index 2eb0b9a64f..cbac58e532 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} 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 dbe3e6f62d..93fa81bee8 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/tsconfig.json +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 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 @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/vanilla-js/react-vanilla-custom-styles/main.tsx b/examples/vanilla-js/react-vanilla-custom-styles/main.tsx index 677c7f7eed..1260513388 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/main.tsx +++ b/examples/vanilla-js/react-vanilla-custom-styles/main.tsx @@ -7,5 +7,5 @@ const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/vanilla-js/react-vanilla-custom-styles/package.json b/examples/vanilla-js/react-vanilla-custom-styles/package.json index f192b05a60..cbb7d331ed 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/package.json +++ b/examples/vanilla-js/react-vanilla-custom-styles/package.json @@ -5,10 +5,10 @@ "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build:prod": "tsc && vite build", - "preview": "vite preview" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { "@blocknote/ariakit": "latest", @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" } -} \ No newline at end of file +} diff --git a/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json b/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json index dbe3e6f62d..93fa81bee8 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json +++ b/examples/vanilla-js/react-vanilla-custom-styles/tsconfig.json @@ -3,14 +3,9 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -22,9 +17,7 @@ "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} 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 f62ab20bc2..0133a6da9e 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/vite.config.ts +++ b/examples/vanilla-js/react-vanilla-custom-styles/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/nx.json b/nx.json deleted file mode 100644 index 627efef773..0000000000 --- a/nx.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "./node_modules/nx/schemas/nx-schema.json", - "parallel": 6, - "release": { - "projects": ["packages/*"], - "version": { - "generatorOptions": { - "fallbackCurrentVersionResolver": "disk" - }, - "preVersionCommand": "pnpm exec nx run-many -t build", - "conventionalCommits": true - }, - "changelog": { - "workspaceChangelog": { - "createRelease": "github" - } - } - }, - "targetDefaults": { - "build": { - "cache": true, - "dependsOn": ["^build"], - "outputs": ["{projectRoot}/dist", "{projectRoot}/types"] - }, - "build:site": { - "cache": true, - "dependsOn": ["^build"], - "inputs": ["{workspaceRoot}/examples/**/*", "{projectRoot}/**/*"], - "outputs": [ - "{projectRoot}/dist", - "{projectRoot}/.next", - "{projectRoot}/.source", - "{projectRoot}/content/examples", - "{projectRoot}/components/example/generated" - ] - }, - "test": { - "cache": true, - "dependsOn": ["^test"] - }, - "lint": { - "cache": true, - "dependsOn": [] - }, - "e2e": { - "cache": true, - "dependsOn": ["^e2e"] - } - } -} diff --git a/package.json b/package.json index b431e8ce3b..7f2a9eead1 100644 --- a/package.json +++ b/package.json @@ -2,59 +2,63 @@ "name": "root", "type": "module", "devDependencies": { - "@nx/js": "^21.6.5", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", "concurrently": "9.1.2", - "eslint": "^8.57.1", - "eslint-config-react-app": "^7.0.1", "eslint-plugin-import": "^2.32.0", "glob": "^10.5.0", - "nx": "^21.6.5", - "prettier": "^3.6.2", - "prettier-plugin-tailwindcss": "^0.6.14", - "serve": "14.2.4", + "serve": "14.2.6", "typescript": "^5.9.3", - "vitest": "^2.1.9", - "wait-on": "8.0.3" + "vite-plus": "catalog:", + "wait-on": "9.0.5" }, - "pnpm": { - "ignoredBuiltDependencies": [ - "sharp", - "workerd" - ], - "onlyBuiltDependencies": [ - "@parcel/watcher", - "@sentry/cli", - "@tailwindcss/oxide", - "better-sqlite3", - "canvas", - "esbuild", - "msw", - "nx", - "unrs-resolver" - ] - }, - "packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b", + "packageManager": "pnpm@11.5.1+sha512.93f7b57422ea7068257235b4c16eb60762eb68e1dc23723199cc739043ea9be2c4143274a399d8c6defa2b1176226d9ca1c4b63482d6200c1a8fbaa78c1d1485", "private": true, "scripts": { - "dev": "nx run @blocknote/example-editor:dev", - "dev:docs": "nx run docs:dev", - "build": "nx run-many --target=build", - "build:clean": "pnpm run clean && pnpm run gen && pnpm run clean && pnpm run build", - "build:site": "nx run-many --target=build:site", - "clean": "nx run-many --target=clean", - "deploy": "nx release --skip-publish", - "gen": "nx run @blocknote/dev-scripts:gen", - "install-playwright": "cd tests && pnpx playwright install --with-deps", - "e2e": "concurrently --success=first -r --kill-others \"pnpm run start -L\" \"wait-on http://localhost:3000 && cd tests && pnpm exec playwright test $PLAYWRIGHT_CONFIG\"", - "e2e:updateSnaps": "concurrently --success=first -r --kill-others \"pnpm run start -L\" \"wait-on http://localhost:3000 && cd tests && pnpm run test:updateSnaps\"", - "lint": "nx run-many --target=lint", + "dev": "vp run --filter @blocknote/example-editor dev", + "dev:docs": "vp run --filter docs dev", + "build": "vp run -r build", + "build:clean": "vp run clean && vp run gen && vp run clean && vp run build", + "build:site": "vp run -r build:site", + "check": "vp run check --fix", + "clean": "vp run -r clean && vp cache clean", + "deploy": "echo not working:(", + "gen": "vp run --filter @blocknote/dev-scripts gen", + "install-playwright": "cd tests && vp exec playwright install --with-deps", + "e2e:image": "docker build -t blocknote-e2e -f tests/Dockerfile .", + "e2e": "bash tests/docker-run.sh -e CI=1 -- --run", + "e2e:updateSnaps": "bash tests/docker-run.sh -e CI=1 -- --run -u", + "e2e:report": "serve -l 4173 tests/playwright-report", + "lint": "vp lint", "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": "pnpm run build", - "start": "serve playground/dist -c ../serve.json", - "test": "nx run-many --target=test", - "format": "prettier --write \"**/*.{js,jsx,ts,tsx,css,scss,md}\"" - } + "prestart": "vp run build", + "start": "vp run --filter @blocknote/example-editor preview", + "test": "vp run -r test", + "format": "vp fmt", + "prepare": "vp config" + }, + "overrides": { + "msw": "2.11.5", + "ai": "6.0.5", + "@ai-sdk/anthropic": "3.0.2", + "@ai-sdk/openai": "3.0.2", + "@ai-sdk/groq": "3.0.2", + "@ai-sdk/google": "3.0.2", + "@ai-sdk/mistral": "3.0.2", + "@ai-sdk/openai-compatible": "2.0.2", + "@ai-sdk/provider-utils": "4.0.2", + "@ai-sdk/react": "3.0.5", + "@ai-sdk/gateway": "3.0.4", + "@headlessui/react": "^2.2.4", + "@tiptap/core": "^3.0.0", + "@tiptap/pm": "^3.0.0" + }, + "workspaces": [ + "packages/*", + "examples/*/*", + "playground", + "fumadocs", + "docs", + "shared", + "tests" + ] } diff --git a/packages/ariakit/package.json b/packages/ariakit/package.json index 62027fead9..6b6c355ae2 100644 --- a/packages/ariakit/package.json +++ b/packages/ariakit/package.json @@ -11,7 +11,7 @@ "directory": "packages/ariakit" }, "license": "MPL-2.0", - "version": "0.46.2", + "version": "0.51.4", "files": [ "dist", "types", @@ -49,38 +49,30 @@ } }, "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint src --max-warnings 0", + "dev": "vp dev", + "preview": "vp preview", + "lint": "vp lint src", "clean": "rimraf dist && rimraf types" }, "dependencies": { "@ariakit/react": "^0.4.19", - "@blocknote/core": "0.46.2", - "@blocknote/react": "0.46.2" + "@blocknote/core": "workspace:^", + "@blocknote/react": "workspace:^" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "eslint": "^8.57.1", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "react": "^19.2.5", + "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", "typescript": "^5.9.3", - "vite": "^5.4.20", - "vite-plugin-eslint": "^1.8.1", - "vite-plugin-externalize-deps": "^0.8.0" + "vite-plugin-externalize-deps": "^0.10.0", + "vite-plus": "catalog:" }, "peerDependencies": { "react": "^18.0 || ^19.0 || >= 19.0.0-rc", "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc" - }, - "eslintConfig": { - "extends": [ - "../../.eslintrc.json" - ] } } diff --git a/packages/ariakit/src/badge/Badge.tsx b/packages/ariakit/src/badge/Badge.tsx index 43b6cdd7d5..f81ca9ce5a 100644 --- a/packages/ariakit/src/badge/Badge.tsx +++ b/packages/ariakit/src/badge/Badge.tsx @@ -8,7 +8,7 @@ import { import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef } from "react"; +import { type MouseEvent, forwardRef } from "react"; export const Badge = forwardRef< HTMLButtonElement, @@ -36,7 +36,7 @@ export const Badge = forwardRef< isSelected && "bn-ak-primary", )} aria-selected={isSelected === true} - onClick={(event) => onClick?.(event)} + onClick={(event: MouseEvent) => onClick?.(event)} onMouseEnter={onMouseEnter} ref={ref} > diff --git a/packages/ariakit/src/comments/Comment.tsx b/packages/ariakit/src/comments/Comment.tsx index efc1746f20..55ebfe2dba 100644 --- a/packages/ariakit/src/comments/Comment.tsx +++ b/packages/ariakit/src/comments/Comment.tsx @@ -58,7 +58,7 @@ export const Comment = forwardRef< actions, children, edited, - emojiPickerOpen, // Unused + emojiPickerOpen, ...rest } = props; @@ -72,7 +72,8 @@ export const Comment = forwardRef< (showActions === true || showActions === undefined || (showActions === "hover" && hovered) || - focused); + focused || + emojiPickerOpen); return ( { + setActiveId={(activeId: string | null | undefined) => { if (activeId) { setOpenTab(activeId); } diff --git a/packages/ariakit/src/panel/PanelFileInput.tsx b/packages/ariakit/src/panel/PanelFileInput.tsx index 4c6a876931..12aef33147 100644 --- a/packages/ariakit/src/panel/PanelFileInput.tsx +++ b/packages/ariakit/src/panel/PanelFileInput.tsx @@ -5,7 +5,7 @@ import { import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef } from "react"; +import { forwardRef, type ChangeEvent } from "react"; export const PanelFileInput = forwardRef< HTMLInputElement, @@ -24,7 +24,9 @@ export const PanelFileInput = forwardRef< type={"file"} accept={accept} value={value ? value.name : undefined} - onChange={async (e) => onChange?.(e.target.files![0])} + onChange={async (e: ChangeEvent) => + onChange?.(e.target.files![0]) + } placeholder={placeholder} /> diff --git a/packages/ariakit/src/popover/Popover.tsx b/packages/ariakit/src/popover/Popover.tsx index 4b41880b2d..df8e01128b 100644 --- a/packages/ariakit/src/popover/Popover.tsx +++ b/packages/ariakit/src/popover/Popover.tsx @@ -6,7 +6,11 @@ import { import { assertEmpty, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef } from "react"; +import { createContext, forwardRef, useContext } from "react"; + +const PortalRootContext = createContext( + undefined, +); export const PopoverTrigger = forwardRef< HTMLButtonElement, @@ -27,6 +31,8 @@ export const PopoverContent = forwardRef< assertEmpty(rest); + const portalRoot = useContext(PortalRootContext); + return ( {children} @@ -44,7 +51,7 @@ export const PopoverContent = forwardRef< export const Popover = ( props: ComponentProps["Generic"]["Popover"]["Root"], ) => { - const { children, open, onOpenChange, position, ...rest } = props; + const { children, open, onOpenChange, position, portalRoot, ...rest } = props; assertEmpty(rest); @@ -54,7 +61,9 @@ export const Popover = ( setOpen={onOpenChange} placement={position} > - {children} + + {children} + ); }; diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css index 1fde174b11..46917be46b 100644 --- a/packages/ariakit/src/style.css +++ b/packages/ariakit/src/style.css @@ -38,10 +38,6 @@ inset 0 1px 1px 1px var(--shadow); } -.bn-ak-popover { - z-index: 10000; -} - .bn-toolbar .bn-ak-popover { gap: 0.5rem; } diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx index 3bc2ac7bd0..c7e13350c8 100644 --- a/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx +++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx @@ -22,7 +22,6 @@ export const SuggestionMenuItem = forwardRef< itemRef.current.closest(".bn-suggestion-menu, #ai-suggestion-menu")!, ); - if (overflow !== "none") { itemRef.current.scrollIntoView({ block: "nearest" }); } diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx index 987142824b..cca2aeaf34 100644 --- a/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx +++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx @@ -19,7 +19,8 @@ export const SuggestionMenuLoader = forwardRef< height="1em" viewBox="0 -960 960 960" width="1em" - fill="#e8eaed"> + fill="#e8eaed" + >
diff --git a/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx b/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx index 0e164ecc05..81bbde8a62 100644 --- a/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx +++ b/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx @@ -22,7 +22,6 @@ export const GridSuggestionMenuItem = forwardRef< itemRef.current.closest(".bn-grid-suggestion-menu")!, ); - if (overflow !== "none") { itemRef.current.scrollIntoView({ block: "nearest" }); } diff --git a/packages/ariakit/src/toolbar/ToolbarButton.tsx b/packages/ariakit/src/toolbar/ToolbarButton.tsx index 0fddcb5905..cb2bf26cca 100644 --- a/packages/ariakit/src/toolbar/ToolbarButton.tsx +++ b/packages/ariakit/src/toolbar/ToolbarButton.tsx @@ -7,7 +7,7 @@ import { import { assertEmpty, isSafari, mergeCSSClasses } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; -import { forwardRef } from "react"; +import { forwardRef, type MouseEvent } from "react"; type ToolbarButtonProps = ComponentProps["Generic"]["Toolbar"]["Button"]; @@ -46,7 +46,7 @@ export const ToolbarButton = forwardRef( )} // Needed as Safari doesn't focus button elements on mouse down // unlike other browsers. - onMouseDown={(e) => { + onMouseDown={(e: MouseEvent) => { if (isSafari()) { (e.currentTarget as HTMLButtonElement).focus(); } diff --git a/packages/ariakit/tsconfig.json b/packages/ariakit/tsconfig.json index 2769eb7390..1e4b4e3f65 100644 --- a/packages/ariakit/tsconfig.json +++ b/packages/ariakit/tsconfig.json @@ -4,7 +4,7 @@ "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], - "moduleResolution": "Node", + "moduleResolution": "Bundler", "jsx": "react-jsx", "strict": true, "sourceMap": true, diff --git a/packages/ariakit/vite.config.ts b/packages/ariakit/vite.config.ts index 955be23212..7cd1b1e43e 100644 --- a/packages/ariakit/vite.config.ts +++ b/packages/ariakit/vite.config.ts @@ -1,71 +1,91 @@ import react from "@vitejs/plugin-react"; import * as path from "path"; import { webpackStats } from "rollup-plugin-webpack-stats"; -import { defineConfig } from "vite"; +import { defineConfig, type UserConfig } from "vite-plus"; import pkg from "./package.json"; // import eslintPlugin from "vite-plugin-eslint"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - test: { - environment: "jsdom", - setupFiles: ["./vitestSetup.ts"], - }, - plugins: [react(), webpackStats()], - // used so that vitest resolves the core package from the sources instead of the built version - resolve: { - alias: - conf.command === "build" - ? ({} as Record) - : ({ - // load live from sources with live reload working - "@blocknote/core": path.resolve(__dirname, "../core/src/"), - "@blocknote/react": path.resolve(__dirname, "../react/src/"), - } as Record), - }, - build: { - sourcemap: true, - lib: { - entry: { - "blocknote-ariakit": path.resolve(__dirname, "src/index.tsx"), +export default defineConfig( + (conf) => + ({ + run: { + tasks: { + build: { + command: "tsc && vp build", + input: [ + { auto: true }, + { pattern: "!**/*.tsbuildinfo", base: "workspace" }, + ], + }, + }, + }, + test: { + environment: "jsdom", + setupFiles: ["./vitestSetup.ts"], }, - name: "blocknote-ariakit", - 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) => { - if ( - Object.keys({ - ...pkg.dependencies, - ...((pkg as any).peerDependencies || {}), - ...pkg.devDependencies, - }).includes(source) - ) { - return true; - } - return ( - source.startsWith("react/") || - source.startsWith("react-dom/") || - source.startsWith("prosemirror-") || - source.startsWith("@tiptap/") || - source.startsWith("@blocknote/") || - source.startsWith("@shikijs/") || - source.startsWith("node:") - ); + plugins: [react(), webpackStats()], + // used so that vitest resolves the core package from the sources instead of the built version + resolve: { + alias: + conf.command === "build" + ? ({ + // Vite 8's postcss-import can't resolve bare package specifiers in CSS @import + "@blocknote/react/style.css": path.resolve( + __dirname, + "../react/dist/style.css", + ), + } as Record) + : ({ + // load live from sources with live reload working + "@blocknote/core": path.resolve(__dirname, "../core/src/"), + "@blocknote/react": path.resolve(__dirname, "../react/src/"), + } as Record), }, - output: { - // Provide global variables to use in the UMD build - // for externalized deps - globals: { - react: "React", - "react-dom": "ReactDOM", + build: { + sourcemap: true, + lib: { + entry: { + "blocknote-ariakit": path.resolve(__dirname, "src/index.tsx"), + }, + name: "blocknote-ariakit", + cssFileName: "style", + 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) => { + 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("@shikijs/") || + source.startsWith("node:") + ); + }, + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: { + react: "React", + "react-dom": "ReactDOM", + }, + }, }, - interop: "compat", // https://rollupjs.org/migration/#changed-defaults }, - }, - }, -})); + }) as UserConfig, +); diff --git a/packages/code-block/package.json b/packages/code-block/package.json index d5e4ea5521..3da6a3199c 100644 --- a/packages/code-block/package.json +++ b/packages/code-block/package.json @@ -9,7 +9,7 @@ "directory": "packages/code-block" }, "license": "MPL-2.0", - "version": "0.46.2", + "version": "0.51.4", "files": [ "dist", "types", @@ -42,36 +42,29 @@ } }, "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint src --max-warnings 0", - "test": "vitest --run", - "test-watch": "vitest watch" + "dev": "vp dev", + "lint": "vp lint src", + "test": "vp test --run", + "test-watch": "vp test watch", + "clean": "rimraf dist && rimraf types" }, "dependencies": { - "@blocknote/core": "0.46.2", - "@shikijs/core": "^3", - "@shikijs/engine-javascript": "^3", - "@shikijs/langs": "^3", - "@shikijs/langs-precompiled": "^3", - "@shikijs/themes": "^3", - "@shikijs/types": "^3" + "@shikijs/core": "^4", + "@shikijs/engine-javascript": "^4", + "@shikijs/langs-precompiled": "^4", + "@shikijs/themes": "^4" + }, + "optionalDependencies": { + "@shikijs/types": "^4" }, "devDependencies": { - "eslint": "^8.57.1", + "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", "typescript": "^5.9.3", - "vite": "^5.4.20", - "vite-plugin-eslint": "^1.8.1", - "vitest": "^2.1.9" + "vite-plus": "catalog:" }, "peerDependencies": { "@blocknote/core": "workspace:^" }, - "eslintConfig": { - "extends": [ - "../../.eslintrc.json" - ] - }, "gitHead": "37614ab348dcc7faa830a9a88437b37197a2162d" } diff --git a/packages/code-block/src/index.test.ts b/packages/code-block/src/index.test.ts index f5dea4fd4a..f8a87bbdf4 100644 --- a/packages/code-block/src/index.test.ts +++ b/packages/code-block/src/index.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { codeBlockOptions } from "./index.js"; describe("codeBlock", () => { diff --git a/packages/code-block/src/shiki.bundle.ts b/packages/code-block/src/shiki.bundle.ts index 75962c807f..41363e1f00 100644 --- a/packages/code-block/src/shiki.bundle.ts +++ b/packages/code-block/src/shiki.bundle.ts @@ -4,7 +4,7 @@ import type { DynamicImportThemeRegistration, HighlighterGeneric, } from "@shikijs/types"; -import { createdBundledHighlighter } from "@shikijs/core"; +import { createBundledHighlighter } from "@shikijs/core"; import { createJavaScriptRegexEngine } from "@shikijs/engine-javascript"; type BundledLanguage = "typescript" | "ts" | "javascript" | "js" | "vue"; @@ -77,8 +77,7 @@ const bundledLanguages = { rust: () => import("@shikijs/langs-precompiled/rust"), rs: () => import("@shikijs/langs-precompiled/rust"), scala: () => import("@shikijs/langs-precompiled/scala"), - // Swift does not support pre-compilation right now - swift: () => import("@shikijs/langs/swift"), + swift: () => import("@shikijs/langs-precompiled/swift"), kotlin: () => import("@shikijs/langs-precompiled/kotlin"), kt: () => import("@shikijs/langs-precompiled/kotlin"), kts: () => import("@shikijs/langs-precompiled/kotlin"), @@ -91,7 +90,7 @@ const bundledThemes = { "github-light": () => import("@shikijs/themes/github-light"), } as Record; -const createHighlighter = /* @__PURE__ */ createdBundledHighlighter< +const createHighlighter = /* @__PURE__ */ createBundledHighlighter< BundledLanguage, BundledTheme >({ diff --git a/packages/code-block/vite.config.ts b/packages/code-block/vite.config.ts index 4eeea8794d..f4fe073f25 100644 --- a/packages/code-block/vite.config.ts +++ b/packages/code-block/vite.config.ts @@ -1,68 +1,79 @@ import * as path from "path"; import { webpackStats } from "rollup-plugin-webpack-stats"; -import { defineConfig } from "vite"; +import { defineConfig, type UserConfig } from "vite-plus"; import pkg from "./package.json"; // import eslintPlugin from "vite-plugin-eslint"; - - // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - test: { - setupFiles: ["./vitestSetup.ts"], - }, - 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) - : ({ - // load live from sources with live reload working - "@blocknote/core": path.resolve(__dirname, "../core/src/"), - "@blocknote/react": path.resolve(__dirname, "../react/src/"), - } as Record), - }, - build: { - sourcemap: true, - lib: { - entry: { - "blocknote-code-block": path.resolve(__dirname, "src/index.ts"), +export default defineConfig( + (conf) => + ({ + run: { + tasks: { + build: { + command: "tsc && vp build", + input: [ + { auto: true }, + { pattern: "!**/*.tsbuildinfo", base: "workspace" }, + ], + }, + }, + }, + test: { + setupFiles: ["./vitestSetup.ts"], }, - name: "blocknote-code-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) => { - if ( - Object.keys({ - ...pkg.dependencies, - ...((pkg as any).peerDependencies || {}), - ...pkg.devDependencies, - }).includes(source) - ) { - return true; - } - return ( - source.startsWith("react/") || - source.startsWith("react-dom/") || - source.startsWith("prosemirror-") || - source.startsWith("@tiptap/") || - source.startsWith("@blocknote/") || - source.startsWith("@shikijs/") || - source.startsWith("node:") - ); + 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) + : ({ + // load live from sources with live reload working + "@blocknote/core": path.resolve(__dirname, "../core/src/"), + "@blocknote/react": path.resolve(__dirname, "../react/src/"), + } as Record), }, - output: { - // Provide global variables to use in the UMD build - // for externalized deps - globals: {}, - interop: "compat", // https://rollupjs.org/migration/#changed-defaults + build: { + sourcemap: true, + lib: { + entry: { + "blocknote-code-block": path.resolve(__dirname, "src/index.ts"), + }, + name: "blocknote-code-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) => { + 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("@shikijs/") || + source.startsWith("node:") + ); + }, + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: {}, + }, + }, }, - }, - }, -})); + }) as UserConfig, +); diff --git a/packages/code-block/vitestSetup.ts b/packages/code-block/vitestSetup.ts index a946b5fc3a..dbcf3eb39c 100644 --- a/packages/code-block/vitestSetup.ts +++ b/packages/code-block/vitestSetup.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach } from "vitest"; +import { afterEach, beforeEach } from "vite-plus/test"; beforeEach(() => { globalThis.window = globalThis.window || ({} as any); diff --git a/packages/core/package.json b/packages/core/package.json index 4d6bd95541..69b76bb07f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,7 +11,7 @@ "directory": "packages/core" }, "license": "MPL-2.0", - "version": "0.46.2", + "version": "0.51.4", "files": [ "dist", "types", @@ -79,82 +79,48 @@ } }, "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", - "preview": "vite preview", - "lint": "eslint src --max-warnings 0", - "test": "vitest --run", - "test-watch": "vitest watch", - "clean": "rimraf dist && rimraf types" + "dev": "vp dev", + "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", + "test-watch": "vp test watch", + "clean": "rimraf dist && rimraf types", + "update-tlds": "node scripts/update-tlds.mjs" }, "dependencies": { "@emoji-mart/data": "^1.2.1", "@handlewithcare/prosemirror-inputrules": "^0.1.4", - "@shikijs/types": "^3", + "@shikijs/types": "^4", "@tanstack/store": "^0.7.7", "@tiptap/core": "^3.13.0", "@tiptap/extension-bold": "^3.13.0", "@tiptap/extension-code": "^3.13.0", - "@tiptap/extensions": "^3.13.0", - "@tiptap/extension-horizontal-rule": "^3.13.0", "@tiptap/extension-italic": "^3.13.0", - "@tiptap/extension-link": "^3.13.0", - "@tiptap/extension-paragraph": "^3.13.0", "@tiptap/extension-strike": "^3.13.0", "@tiptap/extension-text": "^3.13.0", "@tiptap/extension-underline": "^3.13.0", + "@tiptap/extensions": "^3.13.0", "@tiptap/pm": "^3.13.0", "emoji-mart": "^5.6.0", "fast-deep-equal": "^3.1.3", - "hast-util-from-dom": "^5.0.1", - "prosemirror-dropcursor": "^1.8.2", - "prosemirror-highlight": "^0.13.0", + "lib0": "^0.2.99", + "prosemirror-highlight": "^0.15.1", "prosemirror-model": "^1.25.4", "prosemirror-state": "^1.4.4", "prosemirror-tables": "^1.8.3", - "prosemirror-transform": "^1.10.5", + "prosemirror-transform": "^1.11.0", "prosemirror-view": "^1.41.4", - "rehype-format": "^5.0.1", - "rehype-parse": "^9.0.1", - "rehype-remark": "^10.0.1", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.2", - "remark-stringify": "^11.0.0", - "unified": "^11.0.5", - "unist-util-visit": "^5.0.0", - "uuid": "^8.3.2", "y-prosemirror": "^1.3.7", "y-protocols": "^1.0.6", "yjs": "^13.6.27" }, "devDependencies": { - "@types/emoji-mart": "^3.0.14", - "@types/hast": "^3.0.4", - "@types/uuid": "^8.3.4", - "eslint": "^8.57.1", - "jsdom": "^25.0.1", + "jsdom": "^29.0.2", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", "typescript": "^5.9.3", - "vite": "^5.4.20", - "vite-plugin-eslint": "^1.8.1", - "vitest": "^2.1.9" - }, - "peerDependencies": { - "@hocuspocus/provider": "^2.15.2 || ^3.0.0" - }, - "peerDependenciesMeta": { - "@hocuspocus/provider": { - "optional": true - } - }, - "eslintConfig": { - "extends": [ - "../../.eslintrc.json" - ] + "vite-plus": "catalog:" }, "gitHead": "37614ab348dcc7faa830a9a88437b37197a2162d" } diff --git a/packages/core/scripts/update-tlds.mjs b/packages/core/scripts/update-tlds.mjs new file mode 100644 index 0000000000..43f4d02e15 --- /dev/null +++ b/packages/core/scripts/update-tlds.mjs @@ -0,0 +1,135 @@ +#!/usr/bin/env node +/** + * Regenerate src/extensions/tiptap-extensions/Link/helpers/tlds.ts from IANA's + * authoritative TLD list. + * + * Run with: pnpm --filter @blocknote/core update-tlds + * + * Encoding format ported from linkifyjs (MIT, https://github.com/nfrasser/linkifyjs): + * a sorted TLD list is built into a trie, then serialized as an ASCII string + * where letters descend the trie and digit runs mean "emit a word and pop N + * levels back up." Shared TLD prefixes (e.g. construction/consulting/ + * contractors) collapse, producing a payload smaller than a flat list. + * + * IDN punycode entries (XN--...) are skipped: the schemeless URL regex in + * linkDetector.ts requires ASCII-only TLDs, so unicode TLDs would never reach + * the validation step. + */ + +import { writeFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +const TLDS_URL = "https://data.iana.org/TLD/tlds-alpha-by-domain.txt"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const OUT_PATH = resolve( + __dirname, + "../src/extensions/tiptap-extensions/Link/helpers/tlds.ts", +); + +function createTrie(words) { + const root = {}; + for (const word of words) { + let current = root; + for (const letter of word) { + if (!(letter in current)) { + current[letter] = {}; + } + current = current[letter]; + } + current.isWord = true; + } + return root; +} + +function encodeTrieHelper(trie) { + const output = []; + for (const k in trie) { + if (k === "isWord") { + output.push(0); + continue; + } + output.push(k); + output.push(...encodeTrieHelper(trie[k])); + if (typeof output[output.length - 1] === "number") { + output[output.length - 1] += 1; + } else { + output.push(1); + } + } + return output; +} + +function encodeTlds(tlds) { + return encodeTrieHelper(createTrie(tlds)).join(""); +} + +function decodeTlds(encoded) { + const words = []; + const stack = []; + let i = 0; + const digits = "0123456789"; + while (i < encoded.length) { + let popDigitCount = 0; + while (digits.indexOf(encoded[i + popDigitCount]) >= 0) { + popDigitCount++; + } + if (popDigitCount > 0) { + words.push(stack.join("")); + let popCount = parseInt(encoded.substring(i, i + popDigitCount), 10); + while (popCount-- > 0) { + stack.pop(); + } + i += popDigitCount; + } else { + stack.push(encoded[i]); + i++; + } + } + return words; +} + +async function main() { + console.log(`Fetching ${TLDS_URL}...`); + const response = await fetch(TLDS_URL); + if (!response.ok) { + throw new Error(`Failed to fetch IANA TLDs: ${response.status}`); + } + const body = await response.text(); + + const tlds = body + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#") && !/^XN--/i.test(line)) + .map((line) => line.toLowerCase()) + .sort(); + + console.log(`Encoding ${tlds.length} TLDs...`); + const encoded = encodeTlds(tlds); + + console.log("Round-trip asserting..."); + const decoded = decodeTlds(encoded); + if (JSON.stringify(decoded) !== JSON.stringify(tlds)) { + throw new Error("Encode/decode round-trip mismatch"); + } + + const fileContents = `// THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY. +// Source: ${TLDS_URL} +// Regenerate with: pnpm --filter @blocknote/core update-tlds +// Encoding format ported from linkifyjs (MIT) — trie collapsed into ASCII. + +export const ENCODED_TLDS = + "${encoded}"; +`; + + writeFileSync(OUT_PATH, fileContents); + console.log( + `Wrote ${OUT_PATH} (${encoded.length} chars, ${tlds.length} TLDs)`, + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap index dd575d1041..e854849d11 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap @@ -664,7 +664,7 @@ exports[`Test insertBlocks > Insert multiple blocks after 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1339,7 +1339,7 @@ exports[`Test insertBlocks > Insert multiple blocks before 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1934,7 +1934,7 @@ exports[`Test insertBlocks > Insert single basic block after 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2541,7 +2541,7 @@ exports[`Test insertBlocks > Insert single basic block before (without type) 2`] { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3136,7 +3136,7 @@ exports[`Test insertBlocks > Insert single basic block before 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3847,7 +3847,7 @@ exports[`Test insertBlocks > Insert single complex block after 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4558,7 +4558,7 @@ exports[`Test insertBlocks > Insert single complex block before 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts index 409f0e6abb..a42fbdb331 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { setupTestEnv } from "../../setupTestEnv.js"; import { insertBlocks as insertBlocksTr } from "./insertBlocks.js"; diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index 81390947bf..25debee60c 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -26,9 +26,11 @@ export function insertBlocks< const id = typeof referenceBlock === "string" ? referenceBlock : referenceBlock.id; const pmSchema = getPmSchema(tr); - const nodesToInsert = blocksToInsert.map((block) => - blockToNode(block, pmSchema), - ); + const nodesToInsert = blocksToInsert.map((block) => { + const node = blockToNode(block, pmSchema); + node.check(); // `blockToNode` is lenient; validate before mutating the doc + return node; + }); const posInfo = getNodeById(id, tr.doc); if (!posInfo) { diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 690c00017e..20c94c5ab8 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -540,7 +540,7 @@ exports[`Test mergeBlocks > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1091,7 +1091,7 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1642,7 +1642,7 @@ exports[`Test mergeBlocks > First block has children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2192,7 +2192,7 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2749,7 +2749,7 @@ exports[`Test mergeBlocks > Second block is empty 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 57ec776d3a..2491616e29 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -1,8 +1,8 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { getBlockInfoFromTransaction } from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; -import { mergeBlocksCommand } from "./mergeBlocks.js"; +import { getParentBlockInfo, mergeBlocksCommand } from "./mergeBlocks.js"; const getEditor = setupTestEnv(); @@ -77,6 +77,20 @@ describe("Test mergeBlocks", () => { expect(anchorIsAtOldFirstBlockEndPos).toBeTruthy(); }); + it("getParentBlockInfo returns undefined for top-level block", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + const beforePos = getPosBeforeSelectedBlock(); + const doc = getEditor()._tiptapEditor.state.doc; + const $pos = doc.resolve(beforePos); + + expect($pos.depth - 1).toBeLessThan(1); + + const result = getParentBlockInfo(doc, beforePos); + + expect(result).toBeUndefined(); + }); + // We expect a no-op for each of the remaining tests as merging should only // happen for blocks which both have inline content. We also expect // `mergeBlocks` to return false as TipTap commands should do that instead of diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index 50f8aa346c..ce1a9455db 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -10,22 +10,32 @@ import { * Returns the block info from the parent block * or undefined if we're at the root */ -export const getParentBlockInfo = (doc: Node, beforePos: number) => { +export const getParentBlockInfo = ( + doc: Node, + beforePos: number, +): BlockInfo | undefined => { const $pos = doc.resolve(beforePos); + const depth = $pos.depth - 1; - if ($pos.depth <= 1) { + if (depth < 1) { return undefined; } - // get start pos of parent - const parentBeforePos = $pos.posAtIndex( - $pos.index($pos.depth - 1), - $pos.depth - 1, - ); + const parentBeforePos = $pos.before(depth); + const parentNode = doc.resolve(parentBeforePos).nodeAfter; + + if (!parentNode) { + return undefined; + } + + if (!parentNode.type.spec.group?.includes("bnBlock")) { + return getParentBlockInfo(doc, parentBeforePos); + } const parentBlockInfo = getBlockInfoFromResolvedPos( doc.resolve(parentBeforePos), ); + return parentBlockInfo; }; @@ -50,6 +60,27 @@ export const getPrevBlockInfo = (doc: Node, beforePos: number) => { return prevBlockInfo; }; +/** + * Returns the block info from the sibling block after (below) the given block, + * or undefined if the given block is the last sibling. + */ +export const getNextBlockInfo = (doc: Node, beforePos: number) => { + const $pos = doc.resolve(beforePos); + + const indexInParent = $pos.index(); + + if (indexInParent === $pos.node().childCount - 1) { + return undefined; + } + + const nextBlockBeforePos = $pos.posAtIndex(indexInParent + 1); + + const nextBlockInfo = getBlockInfoFromResolvedPos( + doc.resolve(nextBlockBeforePos), + ); + return nextBlockInfo; +}; + /** * If a block has children like this: * A diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap index 902463bbc1..e59da045ed 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap @@ -557,7 +557,7 @@ exports[`Test moveBlocksDown > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -568,8 +568,25 @@ exports[`Test moveBlocksDown > Basic 1`] = ` ] `; -exports[`Test moveBlocksDown > Into children 1`] = ` +exports[`Test moveBlocksDown > Explicit block argument moves the given block 1`] = ` [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -589,23 +606,6 @@ exports[`Test moveBlocksDown > Into children 1`] = ` }, { "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 1", - "type": "text", - }, - ], - "id": "paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, { "children": [ { @@ -1125,7 +1125,7 @@ exports[`Test moveBlocksDown > Into children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1136,7 +1136,7 @@ exports[`Test moveBlocksDown > Into children 1`] = ` ] `; -exports[`Test moveBlocksDown > Last block 1`] = ` +exports[`Test moveBlocksDown > Explicit block argument with nested block 1`] = ` [ { "children": [], @@ -1172,36 +1172,35 @@ exports[`Test moveBlocksDown > Last block 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "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", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-0", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1213,11 +1212,11 @@ exports[`Test moveBlocksDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "paragraph-with-children", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1693,7 +1692,7 @@ exports[`Test moveBlocksDown > Last block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1704,7 +1703,7 @@ exports[`Test moveBlocksDown > Last block 1`] = ` ] `; -exports[`Test moveBlocksDown > Multiple blocks 1`] = ` +exports[`Test moveBlocksDown > Into children 1`] = ` [ { "children": [], @@ -1724,41 +1723,24 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` "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": [ + "children": [ { - "styles": {}, - "text": "Paragraph 1", - "type": "text", + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", }, - ], - "id": "paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ { "children": [ { @@ -1827,6 +1809,23 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` }, "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": [ @@ -2261,7 +2260,7 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2272,7 +2271,7 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` ] `; -exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] = ` +exports[`Test moveBlocksDown > Last block 1`] = ` [ { "children": [], @@ -2291,23 +2290,6 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] }, "type": "paragraph", }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 2", - "type": "text", - }, - ], - "id": "paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, { "children": [], "content": [ @@ -2378,6 +2360,23 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -2829,7 +2828,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2840,7 +2839,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] ] `; -exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks 1`] = ` [ { "children": [], @@ -2864,15 +2863,15 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, @@ -2951,15 +2950,15 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with props", + "text": "Paragraph 2", "type": "text", }, ], - "id": "paragraph-with-props", + "id": "paragraph-2", "props": { "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -3397,7 +3396,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3408,7 +3407,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` ] `; -exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested block 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] = ` [ { "children": [], @@ -3432,11 +3431,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 2", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3449,22 +3448,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Paragraph 1", "type": "text", }, ], - "id": "paragraph-with-children", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", + "id": "paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3475,15 +3463,33 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo { "children": [ { - "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": "Double Nested Paragraph 0", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-0", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3495,11 +3501,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Paragraph with children", "type": "text", }, ], - "id": "nested-paragraph-0", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3512,28 +3518,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph with props", "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", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", "textAlignment": "center", @@ -3975,7 +3964,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo { "children": [], "content": [], - "id": "0", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3986,7 +3975,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo ] `; -exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` [ { "children": [], @@ -4010,11 +3999,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 2", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4027,15 +4016,15 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 "content": [ { "styles": {}, - "text": "Paragraph with props", + "text": "Paragraph 1", "type": "text", }, ], - "id": "paragraph-with-props", + "id": "paragraph-1", "props": { "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -4097,15 +4086,15 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, @@ -4543,7 +4532,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4554,7 +4543,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 ] `; -exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested block 1`] = ` [ { "children": [], @@ -4609,18 +4598,12 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` }, { "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph with props", - "type": "text", - }, - ], - "id": "paragraph-with-props", + "content": [], + "id": "paragraph-9", "props": { "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -4676,6 +4659,23 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` }, "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": [ @@ -5107,21 +5107,10 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` }, "type": "heading", }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, ] `; -exports[`Test moveBlocksDown > Out of children 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1`] = ` [ { "children": [], @@ -5157,6 +5146,23 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, "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": [ { @@ -5227,23 +5233,6 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, "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": [ @@ -5607,7 +5596,43 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` "type": "paragraph", }, { - "children": [], + "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": { @@ -5640,33 +5665,31 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` "type": "heading", }, { - "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", - }, - ], + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` +[ + { + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 1", + "text": "Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5676,8 +5699,14 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, { "children": [], - "content": [], - "id": "trailing-paragraph", + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5685,21 +5714,16 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlocksUp > Basic 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph with children", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5712,48 +5736,30 @@ exports[`Test moveBlocksUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "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", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-0", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5765,11 +5771,11 @@ exports[`Test moveBlocksUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "paragraph-with-children", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5799,32 +5805,15 @@ exports[`Test moveBlocksUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with props", + "text": "Paragraph 3", "type": "text", }, ], - "id": "paragraph-with-props", + "id": "paragraph-3", "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", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -6245,7 +6234,2277 @@ exports[`Test moveBlocksUp > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksDown > Out of children 1`] = ` +[ + { + "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": "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": [], + "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": [ + { + "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": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksUp > Basic 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "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": "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 moveBlocksUp > Explicit block argument moves the given block 1`] = ` +[ + { + "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": "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", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "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 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 moveBlocksUp > Explicit block argument with nested block 1`] = ` +[ + { + "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": "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": [], + "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", + "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", @@ -6813,7 +9072,7 @@ exports[`Test moveBlocksUp > First block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7381,7 +9640,7 @@ exports[`Test moveBlocksUp > Into children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7949,7 +10208,7 @@ exports[`Test moveBlocksUp > Multiple blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -8517,7 +10776,7 @@ exports[`Test moveBlocksUp > Multiple blocks ending in block with children 1`] = { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -9085,7 +11344,7 @@ exports[`Test moveBlocksUp > Multiple blocks ending in nested block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -9652,7 +11911,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting and ending in nested block { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10220,7 +12479,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting in block with children 1`] { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10787,7 +13046,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting in nested block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -11354,7 +13613,7 @@ exports[`Test moveBlocksUp > Out of children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts index 8c637d5985..fec01f91e6 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts @@ -1,8 +1,11 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; -import { getBlockInfoFromTransaction } from "../../../getBlockInfoFromPos.js"; +import { + getBlockInfoFromTransaction, + getNearestBlockPos, +} from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { moveBlocksDown, @@ -204,6 +207,45 @@ describe("Test moveBlocksUp", () => { expect(getEditor().document).toMatchSnapshot(); }); + + it("Explicit block argument moves the given block", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + moveBlocksUp(getEditor(), "paragraph-2"); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Explicit block argument does not change the selection", () => { + getEditor().setTextCursorPosition("paragraph-1"); + makeSelectionSpanContent("text"); + + moveBlocksUp(getEditor(), "paragraph-2"); + + const { anchor, head } = getEditor().transact((tr) => tr.selection); + const anchorBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, anchor).node.attrs.id, + ); + const headBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, head).node.attrs.id, + ); + expect(anchorBlockId).toBe("paragraph-1"); + expect(headBlockId).toBe("paragraph-1"); + }); + + it("Explicit block argument with first block is a no-op", () => { + const documentBefore = getEditor().document; + + moveBlocksUp(getEditor(), "paragraph-0"); + + expect(getEditor().document).toEqual(documentBefore); + }); + + it("Explicit block argument with nested block", () => { + moveBlocksUp(getEditor(), "nested-paragraph-1"); + + expect(getEditor().document).toMatchSnapshot(); + }); }); describe("Test moveBlocksDown", () => { @@ -232,7 +274,7 @@ describe("Test moveBlocksDown", () => { }); it("Last block", () => { - getEditor().setTextCursorPosition("trailing-paragraph"); + getEditor().setTextCursorPosition("paragraph-9"); moveBlocksDown(getEditor()); @@ -286,4 +328,43 @@ describe("Test moveBlocksDown", () => { expect(getEditor().document).toMatchSnapshot(); }); + + it("Explicit block argument moves the given block", () => { + getEditor().setTextCursorPosition("paragraph-9"); + + moveBlocksDown(getEditor(), "paragraph-0"); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Explicit block argument does not change the selection", () => { + getEditor().setTextCursorPosition("paragraph-1"); + makeSelectionSpanContent("text"); + + moveBlocksDown(getEditor(), "paragraph-0"); + + const { anchor, head } = getEditor().transact((tr) => tr.selection); + const anchorBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, anchor).node.attrs.id, + ); + const headBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, head).node.attrs.id, + ); + expect(anchorBlockId).toBe("paragraph-1"); + expect(headBlockId).toBe("paragraph-1"); + }); + + it("Explicit block argument with last block is a no-op", () => { + const documentBefore = getEditor().document; + + moveBlocksDown(getEditor(), "trailing-paragraph"); + + expect(getEditor().document).toEqual(documentBefore); + }); + + it("Explicit block argument with nested block", () => { + moveBlocksDown(getEditor(), "nested-paragraph-0"); + + expect(getEditor().document).toMatchSnapshot(); + }); }); diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts index 8d4591123e..bb2f08dfca 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts @@ -123,29 +123,50 @@ function updateBlockSelectionFromData( tr.setSelection(selection); } -/** - * Replaces any `columnList` blocks with the children of their columns. This is - * done here instead of in `getSelection` as we still need to remove the entire - * `columnList` node but only insert the `blockContainer` nodes inside it. - * @param blocks The blocks to flatten. - */ +// Replaces top-level `column` blocks with their children, as a `column` is not +// a valid block outside a `columnList`. Other blocks are returned as-is. function flattenColumns( blocks: Block[], ): Block[] { - return blocks - .map((block) => { - if (block.type === "columnList") { - return block.children - .map((column) => flattenColumns(column.children)) - .flat(); + return blocks.flatMap((block) => + block.type === "column" ? block.children : [block], + ); +} + +/** + * Removes the given blocks from the editor, then inserts them before/after a + * reference block. + * @param editor The BlockNote editor instance to move the blocks in. + * @param blocks The blocks to move. + * @param referenceBlock The reference block to insert the blocks before/after. + * @param placement Whether to insert the blocks before or after the reference + * block. + */ +export function moveBlocks( + editor: BlockNoteEditor, + blocks: Block[], + referenceBlock: BlockIdentifier, + placement: "before" | "after", +) { + editor.transact(() => { + // A `columnList` reference can be dissolved by `fixColumnList` when its + // `column`s are removed, leaving its ID invalid for re-insertion. Anchor + // to an adjacent block instead, which is unaffected by the removal. + const refBlock = editor.getBlock(referenceBlock); + if (refBlock?.type === "columnList") { + const adjacent = + placement === "after" + ? editor.getNextBlock(refBlock) + : editor.getPrevBlock(refBlock); + if (adjacent) { + referenceBlock = adjacent; + placement = placement === "after" ? "before" : "after"; } + } - return { - ...block, - children: flattenColumns(block.children), - }; - }) - .flat(); + editor.removeBlocks(blocks); + editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement); + }); } /** @@ -170,8 +191,7 @@ export function moveSelectedBlocksAndSelection( ]; const selectionData = getBlockSelectionData(editor); - editor.removeBlocks(blocks); - editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement); + moveBlocks(editor, blocks, referenceBlock, placement); updateBlockSelectionFromData(tr, selectionData); }); @@ -289,50 +309,91 @@ function getMoveDownPlacement( return { referenceBlock, placement }; } -export function moveBlocksUp(editor: BlockNoteEditor) { +export function moveBlocksUp( + editor: BlockNoteEditor, + blockIdentifier?: BlockIdentifier, +) { editor.transact(() => { - const selection = editor.getSelection(); - const block = selection?.blocks[0] || editor.getTextCursorPosition().block; + let sourceBlock: Block | undefined; + if (blockIdentifier) { + sourceBlock = editor.getBlock(blockIdentifier); + if (!sourceBlock) { + return; + } + } else { + const selection = editor.getSelection(); + sourceBlock = + selection?.blocks[0] || editor.getTextCursorPosition().block; + } const moveUpPlacement = getMoveUpPlacement( editor, - editor.getPrevBlock(block), - editor.getParentBlock(block), + editor.getPrevBlock(sourceBlock), + editor.getParentBlock(sourceBlock), ); if (!moveUpPlacement) { return; } - moveSelectedBlocksAndSelection( - editor, - moveUpPlacement.referenceBlock, - moveUpPlacement.placement, - ); + if (blockIdentifier) { + moveBlocks( + editor, + [sourceBlock], + moveUpPlacement.referenceBlock, + moveUpPlacement.placement, + ); + } else { + moveSelectedBlocksAndSelection( + editor, + moveUpPlacement.referenceBlock, + moveUpPlacement.placement, + ); + } }); } -export function moveBlocksDown(editor: BlockNoteEditor) { +export function moveBlocksDown( + editor: BlockNoteEditor, + blockIdentifier?: BlockIdentifier, +) { editor.transact(() => { - const selection = editor.getSelection(); - const block = - selection?.blocks[selection?.blocks.length - 1] || - editor.getTextCursorPosition().block; + let sourceBlock: Block | undefined; + if (blockIdentifier) { + sourceBlock = editor.getBlock(blockIdentifier); + if (!sourceBlock) { + return; + } + } else { + const selection = editor.getSelection(); + sourceBlock = + selection?.blocks[selection?.blocks.length - 1] || + editor.getTextCursorPosition().block; + } const moveDownPlacement = getMoveDownPlacement( editor, - editor.getNextBlock(block), - editor.getParentBlock(block), + editor.getNextBlock(sourceBlock), + editor.getParentBlock(sourceBlock), ); if (!moveDownPlacement) { return; } - moveSelectedBlocksAndSelection( - editor, - moveDownPlacement.referenceBlock, - moveDownPlacement.placement, - ); + if (blockIdentifier) { + moveBlocks( + editor, + [sourceBlock], + moveDownPlacement.referenceBlock, + moveDownPlacement.placement, + ); + } else { + moveSelectedBlocksAndSelection( + editor, + moveDownPlacement.referenceBlock, + moveDownPlacement.placement, + ); + } }); } diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/__snapshots__/nestBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/nestBlock/__snapshots__/nestBlock.test.ts.snap new file mode 100644 index 0000000000..95a5419dab --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/__snapshots__/nestBlock.test.ts.snap @@ -0,0 +1,952 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`unnestBlock / liftListItem > BLO-835: unnest block with siblings after and nested children > should handle unnesting the first of many siblings 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-835: unnest block with siblings after and nested children > should move siblings after into lifted block's children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 5", + "type": "text", + }, + ], + "id": "block5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-835: unnest block with siblings after and nested children > should not throw when unnesting a block that has siblings after it 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 5", + "type": "text", + }, + ], + "id": "block5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-844/847: unnest with complex nesting after parent operations > should handle sequential unnest operations 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-844/847: unnest with complex nesting after parent operations > should handle unnesting when block is only child 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child", + "type": "text", + }, + ], + "id": "child", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-899: Shift-Tab on second-level nested block > should not throw when unnesting a deeply nested block with siblings 1`] = ` +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 2", + "type": "text", + }, + ], + "id": "grandchild2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Grandchild 1", + "type": "text", + }, + ], + "id": "grandchild1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 3", + "type": "text", + }, + ], + "id": "grandchild3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 4", + "type": "text", + }, + ], + "id": "grandchild4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "child2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-899: Shift-Tab on second-level nested block > should not throw when unnesting the last deeply nested block 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 1", + "type": "text", + }, + ], + "id": "grandchild1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 2", + "type": "text", + }, + ], + "id": "grandchild2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-953: unnest block with multi-level nested children > should preserve all deeply nested content when unnesting 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 5", + "type": "text", + }, + ], + "id": "block5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block A", + "type": "text", + }, + ], + "id": "blockA", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-953: unnest block with multi-level nested children > should preserve content when unnesting only child 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block A", + "type": "text", + }, + ], + "id": "blockA", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > Edge cases > should handle unnesting block with both existing children and siblings after 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Existing Grandchild", + "type": "text", + }, + ], + "id": "existing-grandchild", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "child2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 3", + "type": "text", + }, + ], + "id": "child3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > Edge cases > should handle unnesting with different block types 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph Sibling", + "type": "text", + }, + ], + "id": "para-sibling", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Heading Child", + "type": "text", + }, + ], + "id": "heading-child", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] +`; + +exports[`unnestBlock / liftListItem > nestBlock > should nest a block under its previous sibling 1`] = ` +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > nestBlock > should nest into a sibling that already has children (nestedBefore) 1`] = ` +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.test.ts b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.test.ts new file mode 100644 index 0000000000..8247e9391c --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.test.ts @@ -0,0 +1,661 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { afterAll, beforeAll } from "vite-plus/test"; +import { PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; + +/** + * Custom test setup with a document designed to reproduce nesting/unnesting bugs. + * + * BLO-835 / BLO-899: liftListItem produces invalid content when a nested block + * has siblings after it in the same blockGroup. + * BLO-953: Backspace at start of indented block with multi-level children + * causes deeply nested content to be lost. + * BLO-844 / BLO-847: Deleting parent block then operating on children causes + * RangeError. + */ + +function setupNestTestEnv() { + let editor: BlockNoteEditor; + const div = document.createElement("div"); + + beforeAll(() => { + editor = BlockNoteEditor.create(); + editor.mount(div); + }); + + afterAll(() => { + editor._tiptapEditor.destroy(); + editor = undefined as any; + }); + + return (doc: PartialBlock[]) => { + editor.replaceBlocks(editor.document, doc); + return editor; + }; +} + +const withEditor = setupNestTestEnv(); + +describe("unnestBlock / liftListItem", () => { + // BLO-835: liftListItem error with siblings after nested children + // Structure: + // block1 + // block2 ← unnest this + // block3 + // block4 + // block5 + // + // Expected: block2 lifts out, block5 becomes child of block2 + describe("BLO-835: unnest block with siblings after and nested children", () => { + it("should not throw when unnesting a block that has siblings after it", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + }, + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + { + id: "block5", + type: "paragraph", + content: "Block 5", + }, + ], + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("should move siblings after into lifted block's children", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + { + id: "block5", + type: "paragraph", + content: "Block 5", + }, + ], + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // block2 should now be at root level after block1 + // block5 should be a child of block2 + expect(editor.document).toMatchSnapshot(); + }); + + it("should handle unnesting the first of many siblings", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + { + id: "block3", + type: "paragraph", + content: "Block 3", + }, + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // block2 at root, block3 and block4 become children of block2 + expect(editor.document).toMatchSnapshot(); + }); + }); + + // BLO-899: Shift-Tab on second-level nested child (not last) causes error + // Structure: + // parent + // child1 + // grandchild1 ← unnest this + // grandchild2 + // child2 + // grandchild3 + // grandchild4 + // + describe("BLO-899: Shift-Tab on second-level nested block", () => { + it("should not throw when unnesting a deeply nested block with siblings", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + children: [ + { + id: "grandchild1", + type: "paragraph", + content: "Grandchild 1", + }, + { + id: "grandchild2", + type: "paragraph", + content: "Grandchild 2", + }, + ], + }, + { + id: "child2", + type: "paragraph", + content: "Child 2", + children: [ + { + id: "grandchild3", + type: "paragraph", + content: "Grandchild 3", + }, + { + id: "grandchild4", + type: "paragraph", + content: "Grandchild 4", + }, + ], + }, + ], + }, + ]); + + editor.setTextCursorPosition("grandchild1", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // grandchild1 should become a sibling of child1 (at same level) + // grandchild2 should become a child of grandchild1 + expect(editor.document).toMatchSnapshot(); + }); + + it("should not throw when unnesting the last deeply nested block", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + children: [ + { + id: "grandchild1", + type: "paragraph", + content: "Grandchild 1", + }, + { + id: "grandchild2", + type: "paragraph", + content: "Grandchild 2", + }, + ], + }, + ], + }, + ]); + + // Unnesting the LAST child should always work (no siblings after) + editor.setTextCursorPosition("grandchild2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + }); + + // BLO-953: Backspace at start of indented block loses deeply nested content + // Structure: + // block1 + // blockA "text A" ← Backspace at start (unnest via keyboard) + // block3 + // block4 + // block5 + // + // Expected: blockA moves to root, all children preserved + describe("BLO-953: unnest block with multi-level nested children", () => { + it("should preserve all deeply nested content when unnesting", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "blockA", + type: "paragraph", + content: "Block A", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + children: [ + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + { + id: "block5", + type: "paragraph", + content: "Block 5", + }, + ], + }, + ], + }, + ]); + + editor.setTextCursorPosition("blockA", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + const doc = editor.document; + + // All blocks should still exist in the document + const allBlockIds = flattenBlockIds(doc); + expect(allBlockIds).toContain("block1"); + expect(allBlockIds).toContain("blockA"); + expect(allBlockIds).toContain("block3"); + expect(allBlockIds).toContain("block4"); + expect(allBlockIds).toContain("block5"); + + expect(doc).toMatchSnapshot(); + }); + + it("should preserve content when unnesting only child", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "blockA", + type: "paragraph", + content: "Block A", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + children: [ + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + ], + }, + ], + }, + ]); + + editor.setTextCursorPosition("blockA", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + const doc = editor.document; + const allBlockIds = flattenBlockIds(doc); + expect(allBlockIds).toContain("block1"); + expect(allBlockIds).toContain("blockA"); + expect(allBlockIds).toContain("block3"); + expect(allBlockIds).toContain("block4"); + + expect(doc).toMatchSnapshot(); + }); + }); + + // BLO-844 / BLO-847: Operations after deleting parent cause RangeError + // These bugs manifest when backspace merges/deletes a parent block and + // then further operations on the (now re-parented) children fail. + // + // The core issue is liftListItem failing when the children need to be + // reorganized. Testing the unnest operation directly. + describe("BLO-844/847: unnest with complex nesting after parent operations", () => { + it("should handle unnesting when block is only child", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child", + type: "paragraph", + content: "Child", + }, + ], + }, + ]); + + editor.setTextCursorPosition("child", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("should handle sequential unnest operations", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + }, + ], + }, + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + ]); + + // First unnest block2 + editor.setTextCursorPosition("block2", "start"); + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // Then unnest block3 (which should now be child of block2) + editor.setTextCursorPosition("block3", "start"); + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + }); + + // Additional edge cases + describe("Edge cases", () => { + it("should not unnest a root-level block", () => { + const editor = withEditor([ + { + id: "root-block", + type: "paragraph", + content: "Root Block", + }, + ]); + + editor.setTextCursorPosition("root-block", "start"); + + // Should be a no-op (can't unnest root level) + const canUnnest = editor.canUnnestBlock(); + expect(canUnnest).toBe(false); + }); + + it("should handle unnesting block with both existing children and siblings after", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + children: [ + { + id: "existing-grandchild", + type: "paragraph", + content: "Existing Grandchild", + }, + ], + }, + { + id: "child2", + type: "paragraph", + content: "Child 2", + }, + { + id: "child3", + type: "paragraph", + content: "Child 3", + }, + ], + }, + ]); + + editor.setTextCursorPosition("child1", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // child1 should be at root level + // existing-grandchild should still be a child of child1 + // child2 and child3 should also become children of child1 + const doc = editor.document; + const allBlockIds = flattenBlockIds(doc); + expect(allBlockIds).toContain("parent"); + expect(allBlockIds).toContain("child1"); + expect(allBlockIds).toContain("existing-grandchild"); + expect(allBlockIds).toContain("child2"); + expect(allBlockIds).toContain("child3"); + + expect(doc).toMatchSnapshot(); + }); + + it("should handle unnesting with different block types", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "heading-child", + type: "heading", + content: "Heading Child", + }, + { + id: "para-sibling", + type: "paragraph", + content: "Paragraph Sibling", + }, + ], + }, + ]); + + editor.setTextCursorPosition("heading-child", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + }); + + // nestBlock tests (sinkListItem) - ensuring nesting works correctly + describe("nestBlock", () => { + it("should nest a block under its previous sibling", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + }, + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + editor.nestBlock(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("should not nest the first block (no previous sibling)", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + }, + ]); + + editor.setTextCursorPosition("block1", "start"); + + const canNest = editor.canNestBlock(); + expect(canNest).toBe(false); + }); + + it("should nest into a sibling that already has children (nestedBefore)", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + }, + ], + }, + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + editor.nestBlock(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("nest then unnest should be a round trip", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + }, + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + ]); + + const originalDoc = JSON.parse(JSON.stringify(editor.document)); + + editor.setTextCursorPosition("block2", "start"); + editor.nestBlock(); + editor.unnestBlock(); + + // Content should be preserved (IDs may differ but structure/content same) + expect(editor.document.length).toBe(originalDoc.length); + expect(editor.document[0].content).toEqual(originalDoc[0].content); + expect(editor.document[1].content).toEqual(originalDoc[1].content); + }); + }); +}); + +/** Recursively collects all block IDs from a document */ +function flattenBlockIds(blocks: any[]): string[] { + const ids: string[] = []; + for (const block of blocks) { + if (block.id) { + ids.push(block.id); + } + if (block.children) { + ids.push(...flattenBlockIds(block.children)); + } + } + return ids; +} diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts index e8e97d77d3..1540bbed74 100644 --- a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts @@ -1,27 +1,27 @@ -import { Fragment, NodeType, Slice } from "prosemirror-model"; +import { Fragment, NodeRange, NodeType, Slice } from "prosemirror-model"; import { Transaction } from "prosemirror-state"; -import { ReplaceAroundStep } from "prosemirror-transform"; +import { canJoin, liftTarget, ReplaceAroundStep } from "prosemirror-transform"; import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { getBlockInfoFromTransaction } from "../../../getBlockInfoFromPos.js"; -// TODO: Unit tests /** - * This is a modified version of https://github.com/ProseMirror/prosemirror-schema-list/blob/569c2770cbb8092d8f11ea53ecf78cb7a4e8f15a/src/schema-list.ts#L232 + * Modified version of prosemirror-schema-list's sinkItem. + * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts * - * The original function derives too many information from the parentnode and itemtype + * Changes from the original: + * 1. Range predicate checks node.type instead of firstChild.type + * 2. nestedBefore checks groupType instead of parent.type + * 3. Slice creates groupType instead of parent.type + * 4. Operates on Transaction directly instead of state+dispatch */ -function sinkListItem( - tr: Transaction, - itemType: NodeType, - groupType: NodeType, -) { +function sinkItem(tr: Transaction, itemType: NodeType, groupType: NodeType) { const { $from, $to } = tr.selection; const range = $from.blockRange( $to, (node) => node.childCount > 0 && - (node.type.name === "blockGroup" || node.type.name === "column"), // change necessary to not look at first item child type + (node.type.name === "blockGroup" || node.type.name === "column"), // change 1 ); if (!range) { return false; @@ -36,11 +36,11 @@ function sinkListItem( return false; } const nestedBefore = - nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type + nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change 2 const inner = Fragment.from(nestedBefore ? itemType.create() : null); const slice = new Slice( Fragment.from( - itemType.create(null, Fragment.from(groupType.create(null, inner))), // change necessary to create "groupType" instead of parent.type + itemType.create(null, Fragment.from(groupType.create(null, inner))), // change 3 ), nestedBefore ? 3 : 1, 0, @@ -66,7 +66,7 @@ function sinkListItem( export function nestBlock(editor: BlockNoteEditor) { return editor.transact((tr) => { - return sinkListItem( + return sinkItem( tr, editor.pmSchema.nodes["blockContainer"], editor.pmSchema.nodes["blockGroup"], @@ -74,8 +74,121 @@ export function nestBlock(editor: BlockNoteEditor) { }); } +/** + * Modified version of prosemirror-schema-list's liftToOuterList. + * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts + * + * Changes from the original: + * 1. Operates on Transaction directly instead of state+dispatch (TipTap compat) + * 2. When the lifted block already has children (a groupType child), uses deeper + * openStart/offset so siblings merge into the existing group instead of + * creating a second one (which would violate blockContainer's schema) + * 3. Uses groupType.create() instead of range.parent.copy() (same as sinkItem) + */ +function liftToOuterList( + tr: Transaction, + itemType: NodeType, + groupType: NodeType, // change 3 + range: NodeRange, +) { + const end = range.end; + const endOfList = range.$to.end(range.depth); + + if (end < endOfList) { + // There are siblings after the lifted items, which must become + // children of the last item + const blockBeingLifted = range.parent.child(range.endIndex - 1); + const nestedAfter = + blockBeingLifted.lastChild && + blockBeingLifted.lastChild.type === groupType; // change 2 + + tr.step( + new ReplaceAroundStep( + end - (nestedAfter ? 2 : 1), // change 2: go deeper when merging into existing children + endOfList, + end, + endOfList, + new Slice( + Fragment.from( + itemType.create(null, groupType.create()), // change 3 + ), + nestedAfter ? 2 : 1, // change 2: open deeper when merging into existing children + 0, + ), + nestedAfter ? 0 : 1, // change 2: Slice.insertAt offsets by openStart, so 0+2=2 lands inside existing bg + true, + ), + ); + range = new NodeRange( + tr.doc.resolve(range.$from.pos), + tr.doc.resolve(endOfList), + range.depth, + ); + } + + const target = liftTarget(range); + if (target == null) { + return false; + } + + tr.lift(range, target); + + const $after = tr.doc.resolve(tr.mapping.map(end, -1) - 1); + if ( + canJoin(tr.doc, $after.pos) && + $after.nodeBefore!.type === $after.nodeAfter!.type + ) { + tr.join($after.pos); + } + + tr.scrollIntoView(); + return true; +} + +/** + * Modified version of prosemirror-schema-list's liftListItem. + * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts + * + * Changes from the original: + * 1. Range predicate checks node.type instead of firstChild.type (same as sinkItem) + * 2. Passes groupType to liftToOuterList + * 3. Operates on Transaction directly instead of state+dispatch + * 4. Skips liftOutOfList (root-level blocks can't be unnested in BlockNote) + */ +export function liftItem( + tr: Transaction, + itemType: NodeType, + groupType: NodeType, // change 2 +) { + const { $from, $to } = tr.selection; + const range = $from.blockRange( + $to, + (node) => + node.childCount > 0 && + (node.type.name === "blockGroup" || node.type.name === "column"), // change 1 + ); + if (!range) { + return false; + } + + if ($from.node(range.depth - 1).type === itemType) { + // Inside a parent node + return liftToOuterList(tr, itemType, groupType, range); // change 2 + } + + // This is the "liftOutOfList" path — lifting out of a list entirely. + // Not applicable to BlockNote (root-level blocks can't be unnested). // change 4 + return false; +} + export function unnestBlock(editor: BlockNoteEditor) { - editor._tiptapEditor.commands.liftListItem("blockContainer"); + return editor.transact((tr) => + liftItem( + tr, + editor.pmSchema.nodes["blockContainer"], + editor.pmSchema.nodes["blockGroup"], + ), + ); } export function canNestBlock(editor: BlockNoteEditor) { diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap index d255acf235..d876b31175 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap @@ -470,7 +470,7 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -767,7 +767,7 @@ exports[`Test replaceBlocks > Remove multiple non-consecutive blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1318,7 +1318,7 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1850,7 +1850,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2342,7 +2342,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2892,7 +2892,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3240,7 +3240,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with multi { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3548,7 +3548,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3914,7 +3914,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4516,7 +4516,7 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5078,7 +5078,7 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5698,7 +5698,7 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts index ecdc1e49a2..5a968c49bf 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { setupTestEnv } from "../../setupTestEnv.js"; import { removeAndInsertBlocks } from "./replaceBlocks.js"; diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts index 04a2425a33..f1e946f909 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -27,9 +27,11 @@ export function removeAndInsertBlocks< const pmSchema = getPmSchema(tr); // Converts the `PartialBlock`s to ProseMirror nodes to insert them into the // document. - const nodesToInsert: Node[] = blocksToInsert.map((block) => - blockToNode(block, pmSchema), - ); + const nodesToInsert: Node[] = blocksToInsert.map((block) => { + const node = blockToNode(block, pmSchema); + node.check(); // `blockToNode` is lenient; validate before mutating the doc + return node; + }); const idsOfBlocksToRemove = new Set( blocksToRemove.map((block) => diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap index 60c3d1c1ed..8cd297eaee 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap @@ -574,7 +574,7 @@ exports[`Test splitBlocks > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1159,7 +1159,7 @@ exports[`Test splitBlocks > Block has children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1744,7 +1744,7 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2329,7 +2329,7 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2908,7 +2908,7 @@ exports[`Test splitBlocks > End of content 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3495,7 +3495,7 @@ exports[`Test splitBlocks > Keep type 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts index 4165ebc98d..b4b4c05a04 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -1,6 +1,6 @@ import { Node } from "prosemirror-model"; import { TextSelection } from "prosemirror-state"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { getBlockInfo, 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 3246168815..e4559884da 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 @@ -629,7 +629,7 @@ exports[`Test updateBlock > Revert all props 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1269,7 +1269,7 @@ exports[`Test updateBlock > Revert single prop 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1909,7 +1909,7 @@ exports[`Test updateBlock > Update all props 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2549,7 +2549,7 @@ exports[`Test updateBlock > Update children 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3131,7 +3131,7 @@ exports[`Test updateBlock > Update inline content to no content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4055,7 +4055,7 @@ exports[`Test updateBlock > Update inline content to table content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4634,7 +4634,7 @@ exports[`Test updateBlock > Update no content to empty inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5217,7 +5217,7 @@ exports[`Test updateBlock > Update no content to empty table content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5808,7 +5808,7 @@ exports[`Test updateBlock > Update no content to inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6735,7 +6735,7 @@ exports[`Test updateBlock > Update no content to table content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7303,7 +7303,7 @@ exports[`Test updateBlock > Update partial (offset start + end) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7864,7 +7864,7 @@ exports[`Test updateBlock > Update partial (offset start) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -8425,7 +8425,7 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -8993,7 +8993,7 @@ exports[`Test updateBlock > Update partial (table cell) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -9561,7 +9561,7 @@ exports[`Test updateBlock > Update partial (table row) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10201,7 +10201,7 @@ exports[`Test updateBlock > Update single prop 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10609,7 +10609,7 @@ exports[`Test updateBlock > Update table content to empty inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -11029,7 +11029,7 @@ exports[`Test updateBlock > Update table content to inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -11443,7 +11443,7 @@ exports[`Test updateBlock > Update table content to no content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -12079,7 +12079,7 @@ exports[`Test updateBlock > Update type 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -12691,7 +12691,7 @@ exports[`Test updateBlock > Update with plain content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -13331,7 +13331,7 @@ exports[`Test updateBlock > Update with styled content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", 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 95ea4dc4e7..132a9de479 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { getBlockInfo } from "../../../getBlockInfoFromPos.js"; import { getNodeById } from "../../../nodeUtil.js"; diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 4318b19ca7..a3e2b3b0db 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -128,16 +128,18 @@ export function updateBlockTr< // for this, we do a nodeToBlock on the existing block to get the children. // it would be cleaner to use a ReplaceAroundStep, but this is a bit simpler and it's quite an edge case const existingBlock = nodeToBlock(blockInfo.bnBlock.node, pmSchema); + const replacementNode = blockToNode( + { + children: existingBlock.children, // if no children are passed in, use existing children + ...block, + }, + pmSchema, + ); + replacementNode.check(); // `blockToNode` is lenient; validate before mutating the doc tr.replaceWith( blockInfo.bnBlock.beforePos, blockInfo.bnBlock.afterPos, - blockToNode( - { - children: existingBlock.children, // if no children are passed in, use existing children - ...block, - }, - pmSchema, - ), + replacementNode, ); return; @@ -278,7 +280,9 @@ function updateChildren< const pmSchema = getPmSchema(tr); if (block.children !== undefined && block.children.length > 0) { const childNodes = block.children.map((child) => { - return blockToNode(child, pmSchema); + const node = blockToNode(child, pmSchema); + node.check(); // `blockToNode` is lenient; validate before mutating the doc + return node; }); // Checks if a blockGroup node already exists. diff --git a/packages/core/src/api/blockManipulation/setupTestEnv.ts b/packages/core/src/api/blockManipulation/setupTestEnv.ts index 37b4807063..c1da2be25e 100644 --- a/packages/core/src/api/blockManipulation/setupTestEnv.ts +++ b/packages/core/src/api/blockManipulation/setupTestEnv.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, beforeEach } from "vitest"; +import { afterAll, beforeAll, beforeEach } from "vite-plus/test"; import { PartialBlock } from "../../blocks/defaultBlocks.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; @@ -185,7 +185,7 @@ const testDocument: PartialBlock[] = [ ], }, { - id: "trailing-paragraph", + id: "paragraph-9", type: "paragraph", }, ]; diff --git a/packages/core/src/api/blockManipulation/tables/tables.test.ts b/packages/core/src/api/blockManipulation/tables/tables.test.ts index b386ea211b..7e72e349a9 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.test.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { Block, DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; import { diff --git a/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts b/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts index b566dfdbe2..ffb298544f 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts @@ -1,9 +1,6 @@ import { EditorView } from "prosemirror-view"; -export async function handleVSCodePaste( - event: ClipboardEvent, - view: EditorView, -) { +export function handleVSCodePaste(event: ClipboardEvent, view: EditorView) { const { schema } = view.state; if (!event.clipboardData) { @@ -17,8 +14,7 @@ export async function handleVSCodePaste( } if (!schema.nodes.codeBlock) { - view.pasteText(text); - return true; + return false; } const vscode = event.clipboardData!.getData("vscode-editor-data"); diff --git a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts index 415b3cb7be..9fa4ed3c55 100644 --- a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts +++ b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts @@ -57,8 +57,13 @@ function defaultPasteHandler({ } if (format === "vscode-editor-data") { - handleVSCodePaste(event, editor.prosemirrorView); - return true; + // If VSCode clipboard data cannot be parsed as a code block, try parsing + // `text/plain` as a fallback. + if (handleVSCodePaste(event, editor.prosemirrorView)) { + return true; + } + + format = "text/plain"; } if (format === "Files") { diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 3a6aeaffd5..e150af1309 100644 --- a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts +++ b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts @@ -140,16 +140,29 @@ export function selectedFragmentToHTML< editor, ); - const markdown = cleanHTMLToMarkdown(externalHTML); + // Code blocks are treated differently for copying: text/plain is the raw + // selected text instead of markdown. + const { $from, $to } = view.state.selection; + const parentBlockType = $from.parent.type.name; + const parentBlockSpec = editor.blockImplementations[parentBlockType as any]; + const isPurelyInsideCodeBlock = + $from.sameParent($to) && + parentBlockSpec?.implementation.meta?.code === true; + + const markdown = isPurelyInsideCodeBlock + ? view.state.doc.textBetween($from.pos, $to.pos) + : cleanHTMLToMarkdown(externalHTML); return { clipboardHTML, externalHTML, markdown }; } -const checkIfSelectionInNonEditableBlock = () => { - // Let browser handle event if selection is empty (nothing - // happens). - const selection = window.getSelection(); - if (!selection || selection.isCollapsed) { +const checkIfSelectionInNonEditableBlock = (view: EditorView) => { + // Use ProseMirror's internal selection state to check for empty selection. + // window.getSelection() returns null or a collapsed selection inside Shadow + // DOM (Firefox, Safari, and Chromium edge cases), causing this guard to + // misfire and silently skip clipboard writes. view.state.selection is always + // accurate regardless of DOM mode. + if (view.state.selection.empty) { return true; } @@ -158,16 +171,19 @@ const checkIfSelectionInNonEditableBlock = () => { // non-editable block. We only need to check one node as it's // not possible for the browser selection to start in an // editable block and end in a non-editable one. - let node = selection.focusNode; - while (node) { - if ( - node instanceof HTMLElement && - node.getAttribute("contenteditable") === "false" - ) { - return true; + const selection = window.getSelection(); + if (selection && !selection.isCollapsed) { + let node = selection.focusNode; + while (node) { + if ( + node instanceof HTMLElement && + node.getAttribute("contenteditable") === "false" + ) { + return true; + } + + node = node.parentElement; } - - node = node.parentElement; } return false; @@ -213,7 +229,7 @@ export const createCopyToClipboardExtension = < props: { handleDOMEvents: { copy(view, event) { - if (checkIfSelectionInNonEditableBlock()) { + if (checkIfSelectionInNonEditableBlock(view)) { return true; } @@ -222,7 +238,7 @@ export const createCopyToClipboardExtension = < return true; }, cut(view, event) { - if (checkIfSelectionInNonEditableBlock()) { + if (checkIfSelectionInNonEditableBlock(view)) { return true; } diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index fb993f8309..72569ffced 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -37,7 +37,7 @@ export function serializeInlineContentExternalHTML< editor: BlockNoteEditor, blockContent: PartialBlock["content"], serializer: DOMSerializer, - options?: { document?: Document }, + options?: { document?: Document; blockType?: string }, ) { let nodes: Node[]; @@ -45,9 +45,17 @@ export function serializeInlineContentExternalHTML< if (!blockContent) { throw new Error("blockContent is required"); } else if (typeof blockContent === "string") { - nodes = inlineContentToNodes([blockContent], editor.pmSchema); + nodes = inlineContentToNodes( + [blockContent], + editor.pmSchema, + options?.blockType, + ); } else if (Array.isArray(blockContent)) { - nodes = inlineContentToNodes(blockContent, editor.pmSchema); + nodes = inlineContentToNodes( + blockContent, + editor.pmSchema, + options?.blockType, + ); } else if (blockContent.type === "tableContent") { nodes = tableContentToNodes(blockContent, editor.pmSchema); } else { @@ -262,7 +270,7 @@ function serializeBlock< editor, block.content as any, // TODO serializer, - options, + { ...options, blockType: block.type }, ); ret.contentDOM.appendChild(ic); @@ -319,7 +327,13 @@ function serializeBlock< } } - if (editor.pmSchema.nodes[block.type as any].isInGroup("blockContent")) { + if ("childrenDOM" in ret && ret.childrenDOM) { + // block specifies where children should go (e.g. toggle blocks + // place children inside
) + ret.childrenDOM.append(childFragment); + } else if ( + editor.pmSchema.nodes[block.type as any].isInGroup("blockContent") + ) { // default "blockContainer" style blocks are flattened (no "nested block" support) for externalHTML, so append the child fragment to the outer fragment fragment.append(childFragment); } else { diff --git a/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts b/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts new file mode 100644 index 0000000000..e74ecb68d3 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts @@ -0,0 +1,849 @@ +/** + * Custom HTML-to-Markdown serializer for BlockNote. + * Replaces the unified/rehype-remark pipeline with a direct DOM-based implementation. + * + * Input: HTML string from createExternalHTMLExporter + * Output: GFM-compatible markdown string + */ + +/** + * Convert an HTML string (from BlockNote's external HTML exporter) to markdown. + */ +export function htmlToMarkdown(html: string): string { + // Use a temporary element to parse HTML. This works in both browser and + // server (JSDOM) environments, unlike `new DOMParser()` which may not be + // globally available in Node.js. + const container = document.createElement("div"); + container.innerHTML = html; + const result = serializeChildren(container, { + indent: "", + inListItem: false, + }); + return result.trim() + "\n"; +} + +interface SerializeContext { + indent: string; // current indentation prefix for list nesting + // True when the current node is being serialized as continuation content + // of a parent list item. Used to suppress trailing blank lines that would + // otherwise turn the parent list into a "loose" list. + inListItem: boolean; +} + +// ─── Main Serializer ───────────────────────────────────────────────────────── + +function serializeChildren(node: Node, ctx: SerializeContext): string { + let result = ""; + const children = Array.from(node.childNodes); + + for (let i = 0; i < children.length; i++) { + const child = children[i]; + result += serializeNode(child, ctx); + } + + return result; +} + +function serializeNode(node: Node, ctx: SerializeContext): string { + if (node.nodeType === 3 /* Node.TEXT_NODE */) { + return node.textContent || ""; + } + + if (node.nodeType !== 1 /* Node.ELEMENT_NODE */) { + return ""; + } + + const el = node as HTMLElement; + const tag = el.tagName.toLowerCase(); + + switch (tag) { + case "p": + return serializeParagraph(el, ctx); + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return serializeHeading(el, ctx); + case "blockquote": + return serializeBlockquote(el, ctx); + case "pre": + return serializeCodeBlock(el, ctx); + case "ul": + return serializeUnorderedList(el, ctx); + case "ol": + return serializeOrderedList(el, ctx); + case "table": + return serializeTable(el, ctx); + case "hr": + return ctx.indent + "***\n\n"; + case "img": + return serializeImage(el, ctx); + case "video": + return serializeVideo(el, ctx); + case "audio": + return serializeAudio(el, ctx); + case "embed": + return serializeEmbed(el, ctx); + case "figure": + return serializeFigure(el, ctx); + case "a": + // Block-level link (file block) + return serializeBlockLink(el, ctx); + case "details": + return serializeDetails(el, ctx); + case "div": + // Page break or generic container — serialize children + return serializeChildren(el, ctx); + case "br": + return ""; + default: + return serializeChildren(el, ctx); + } +} + +// ─── Block Serializers ─────────────────────────────────────────────────────── + +function serializeParagraph(el: HTMLElement, ctx: SerializeContext): string { + const content = serializeInlineContent(el); + // Trim leading/trailing hard breaks (matching remark behavior) + const trimmed = trimHardBreaks(content); + if (ctx.inListItem) { + return trimmed; + } + return ctx.indent + trimmed + "\n\n"; +} + +function serializeHeading(el: HTMLElement, ctx: SerializeContext): string { + const level = parseInt(el.tagName[1], 10); + const prefix = "#".repeat(level) + " "; + const content = serializeInlineContent(el); + return ctx.indent + prefix + content + "\n\n"; +} + +function serializeBlockquote(el: HTMLElement, ctx: SerializeContext): string { + // Check if blockquote contains block-level elements (like

) + const blockChildren = Array.from(el.children).filter((child) => { + const tag = child.tagName.toLowerCase(); + return ["p", "ul", "ol", "pre", "blockquote", "table", "hr"].includes(tag); + }); + + let content: string; + if (blockChildren.length > 0) { + // Has block-level children — serialize each + const parts: string[] = []; + for (const child of blockChildren) { + const tag = child.tagName.toLowerCase(); + if (tag === "p") { + parts.push(serializeInlineContent(child as HTMLElement)); + } else { + const innerCtx: SerializeContext = { indent: "", inListItem: false }; + parts.push(serializeNode(child, innerCtx).trim()); + } + } + content = parts.join("\n\n"); + } else { + // No block-level children — treat entire content as inline + content = serializeInlineContent(el); + } + + const lines = content.split("\n"); + return lines.map((line) => ctx.indent + "> " + line).join("\n") + "\n\n"; +} + +function serializeCodeBlock(el: HTMLElement, ctx: SerializeContext): string { + const codeEl = el.querySelector("code"); + if (!codeEl) { + return ""; + } + + const language = + codeEl.getAttribute("data-language") || + extractLanguageFromClass(codeEl.className) || + ""; + + // Extract code content, handling
elements as newlines + const code = extractCodeContent(codeEl); + + // Use a fence longer than the longest backtick run in the code + const longestRun = Math.max( + 0, + ...(code.match(/`+/g) ?? []).map((run) => run.length), + ); + const fence = "`".repeat(Math.max(3, longestRun + 1)); + + // 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" + + code + + (code.endsWith("\n") ? "" : "\n") + + fence + + "\n\n" + ); +} + +function extractCodeContent(el: Element): string { + let result = ""; + for (const child of Array.from(el.childNodes)) { + if (child.nodeType === 3 /* Node.TEXT_NODE */) { + result += child.textContent || ""; + } else if (child.nodeType === 1 /* Node.ELEMENT_NODE */) { + const tag = (child as HTMLElement).tagName.toLowerCase(); + if (tag === "br") { + result += "\n"; + } else { + result += extractCodeContent(child as Element); + } + } + } + return result; +} + +function extractLanguageFromClass(className: string): string { + const match = className.match(/language-(\S+)/); + return match ? match[1] : ""; +} + +function serializeUnorderedList( + el: HTMLElement, + ctx: SerializeContext, +): string { + let result = ""; + const items = Array.from(el.children).filter( + (child) => child.tagName.toLowerCase() === "li", + ); + + for (const item of items) { + result += serializeListItem(item as HTMLElement, "bullet", ctx); + } + + // Trailing blank line separates the list from the next block. Skip when + // this list is nested inside another list item — adding it would convert + // the parent list into a "loose" list (or break tightness). + if (!ctx.inListItem) { + result += "\n"; + } + return result; +} + +function serializeOrderedList(el: HTMLElement, ctx: SerializeContext): string { + let result = ""; + const items = Array.from(el.children).filter( + (child) => child.tagName.toLowerCase() === "li", + ); + const startNum = parseInt(el.getAttribute("start") || "1", 10); + + for (let i = 0; i < items.length; i++) { + const num = startNum + i; + result += serializeListItem(items[i] as HTMLElement, "ordered", ctx, num); + } + + if (!ctx.inListItem) { + result += "\n"; + } + return result; +} + +function serializeListItem( + el: HTMLElement, + listType: "bullet" | "ordered", + ctx: SerializeContext, + num?: number, +): string { + // Check for checkbox (task list) - direct children only + let checkbox: HTMLInputElement | null = null; + let details: HTMLElement | null = null; + + for (const child of Array.from(el.children)) { + const tag = child.tagName.toLowerCase(); + if (tag === "input" && (child as HTMLInputElement).type === "checkbox") { + checkbox = child as HTMLInputElement; + } + if (tag === "details") { + details = child as HTMLElement; + } + } + + let marker: string; + let markerWidth: number; + + if (checkbox) { + const state = checkbox.checked ? "[x]" : "[ ]"; + marker = `* ${state} `; + // For child indentation, use bullet width (2), not full checkbox marker width + markerWidth = 2; + } else if (listType === "ordered") { + marker = `${num}. `; + markerWidth = marker.length; + } else { + marker = "* "; + markerWidth = 2; + } + + // Collect the item's inline content + let inlineContent: string; + let firstContentEl: Element | null; + + if (details) { + // Toggle item: get content from summary + const summary = details.querySelector("summary"); + const summaryP = summary?.querySelector("p"); + firstContentEl = details; + inlineContent = summaryP ? serializeInlineContent(summaryP) : ""; + } else { + firstContentEl = getFirstContentElement(el, checkbox); + inlineContent = firstContentEl + ? serializeInlineContent(firstContentEl) + : ""; + } + + // The marker line ends with a single `\n` so that consecutive list items + // produce a "tight" list (no blank line between markers). Continuation + // content within the item (nested lists, continuation paragraphs, other + // blocks) injects its own spacing as needed. + let result = ctx.indent + marker + inlineContent + "\n"; + + // Serialize child content (nested lists, continuation paragraphs, etc.) + const childIndent = ctx.indent + " ".repeat(markerWidth); + const childCtx: SerializeContext = { indent: childIndent, inListItem: true }; + + // For toggle items, also serialize children inside the details element + if (details) { + const summary = details.querySelector("summary"); + for (const child of Array.from(details.children)) { + if (child === summary) { + continue; + } + const childTag = child.tagName.toLowerCase(); + if (childTag === "p") { + const content = serializeInlineContent(child as HTMLElement); + // Continuation paragraph needs a blank line to separate it from the + // previous content; CommonMark would otherwise treat it as a soft + // wrap of that content. + result += "\n" + childIndent + content + "\n"; + } else { + result += serializeNode(child, childCtx); + } + } + } + + const children = Array.from(el.children); + for (const child of children) { + const childTag = child.tagName.toLowerCase(); + + // Skip the first content element and checkbox + if (child === firstContentEl || (child as HTMLElement) === checkbox) { + continue; + } + if (childTag === "input") { + continue; + } + + // Nested lists and other block content + if (childTag === "ul" || childTag === "ol") { + // Nested list flows directly under the parent marker — no blank line. + result += serializeNode(child, childCtx); + } else if (childTag === "p") { + // Continuation paragraph within list item — requires blank line before + // so it isn't read as part of the marker line's text. + const content = serializeInlineContent(child as HTMLElement); + result += "\n" + childIndent + content + "\n"; + } else { + // Other block-level children (code blocks, blockquotes, etc.) already + // emit their own separating newlines; prefix with a blank line so they + // are recognized as separate blocks. + result += "\n" + serializeNode(child, childCtx); + } + } + + return result; +} + +function getFirstContentElement( + li: HTMLElement, + checkbox: HTMLInputElement | null, +): HTMLElement | null { + for (const child of Array.from(li.children)) { + if (child === checkbox) { + continue; + } + if (child.tagName.toLowerCase() === "input") { + continue; + } + const tag = child.tagName.toLowerCase(); + if (tag === "p" || tag === "span") { + return child as HTMLElement; + } + } + return null; +} + +// ─── Table Serializer ──────────────────────────────────────────────────────── + +function serializeTable(el: HTMLElement, ctx: SerializeContext): string { + // First, determine column count from colgroup or first row + const colgroup = el.querySelector("colgroup"); + let colCount = 0; + + if (colgroup) { + colCount = colgroup.querySelectorAll("col").length; + } + + const rows: string[][] = []; + let hasHeader = false; + + // Collect all rows, handling colspan/rowspan + const trElements = el.querySelectorAll("tr"); + // Build a grid to handle colspan/rowspan + const grid: (string | null)[][] = []; + + trElements.forEach((tr, rowIdx) => { + if (!grid[rowIdx]) { + grid[rowIdx] = []; + } + const cellElements = tr.querySelectorAll("th, td"); + let gridCol = 0; + + cellElements.forEach((cell) => { + // Find next empty column in this row + while (grid[rowIdx][gridCol] !== undefined) { + gridCol++; + } + + if (rowIdx === 0 && cell.tagName.toLowerCase() === "th") { + hasHeader = true; + } + + const content = escapeTableCell( + serializeInlineContent(cell as HTMLElement).trim(), + ); + const colspan = parseInt(cell.getAttribute("colspan") || "1", 10); + const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10); + + // Fill the grid + for (let r = 0; r < rowspan; r++) { + for (let c = 0; c < colspan; c++) { + const ri = rowIdx + r; + if (!grid[ri]) { + grid[ri] = []; + } + grid[ri][gridCol + c] = r === 0 && c === 0 ? content : ""; + } + } + + gridCol += colspan; + }); + + // Update colCount + if (grid[rowIdx]) { + colCount = Math.max(colCount, grid[rowIdx].length); + } + }); + + // Convert grid to rows + for (const gridRow of grid) { + const row: string[] = []; + for (let c = 0; c < colCount; c++) { + row.push(gridRow && gridRow[c] !== undefined ? (gridRow[c] ?? "") : ""); + } + rows.push(row); + } + + if (rows.length === 0) { + return ""; + } + + // Determine column widths + const colWidths: number[] = []; + for (let c = 0; c < colCount; c++) { + let maxWidth = 3; // minimum width for separator "---" + for (const row of rows) { + const cellWidth = c < row.length ? row[c].length : 0; + maxWidth = Math.max(maxWidth, cellWidth); + } + // Use minimum of 10 to match remark output + colWidths.push(Math.max(maxWidth, 10)); + } + + let result = ""; + + if (hasHeader) { + result += ctx.indent + formatTableRow(rows[0], colWidths, colCount) + "\n"; + result += ctx.indent + formatSeparatorRow(colWidths, colCount) + "\n"; + for (let r = 1; r < rows.length; r++) { + result += + ctx.indent + formatTableRow(rows[r], colWidths, colCount) + "\n"; + } + } else { + // No header — emit empty header + separator + const emptyRow = new Array(colCount).fill(""); + result += ctx.indent + formatTableRow(emptyRow, colWidths, colCount) + "\n"; + result += ctx.indent + formatSeparatorRow(colWidths, colCount) + "\n"; + for (const row of rows) { + result += ctx.indent + formatTableRow(row, colWidths, colCount) + "\n"; + } + } + + result += "\n"; + return result; +} + +function escapeTableCell(text: string): string { + return text.replace(/\|/g, "\\|"); +} + +function formatTableRow( + cells: string[], + colWidths: number[], + colCount: number, +): string { + const parts: string[] = []; + for (let c = 0; c < colCount; c++) { + const cell = c < cells.length ? cells[c] : ""; + parts.push(" " + cell.padEnd(colWidths[c]) + " "); + } + return "|" + parts.join("|") + "|"; +} + +function formatSeparatorRow(colWidths: number[], colCount: number): string { + const parts: string[] = []; + for (let c = 0; c < colCount; c++) { + parts.push(" " + "-".repeat(colWidths[c]) + " "); + } + return "|" + parts.join("|") + "|"; +} + +// ─── Media Serializers ─────────────────────────────────────────────────────── + +function serializeImage(el: HTMLElement, ctx: SerializeContext): string { + const src = el.getAttribute("src") || ""; + const alt = el.getAttribute("alt") || ""; + // Empty placeholder — preserve the block-level break, matching how + // serializeParagraph/serializeHeading emit `\n\n` for empty content. + if (!src) { + return "\n\n"; + } + return ctx.indent + `![${alt}](${src})\n\n`; +} + +function serializeVideo(el: HTMLElement, ctx: SerializeContext): string { + const src = el.getAttribute("src") || el.getAttribute("data-url") || ""; + const name = el.getAttribute("data-name") || el.getAttribute("title") || ""; + if (!src) { + return "\n\n"; + } + return ctx.indent + `![${name}](${src})\n\n`; +} + +function serializeAudio(el: HTMLElement, ctx: SerializeContext): string { + const src = el.getAttribute("src") || ""; + if (!src) { + return "\n\n"; + } + // Audio has no markdown syntax, so emit raw HTML. The markdown parser + // passes

${escapeHtmlText(captionText)}
` + : ""; + return ctx.indent + `
${tag}${captionPart}
\n\n`; +} + +function escapeHtmlAttr(value: string): string { + return value + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); +} + +function escapeHtmlText(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">"); +} + +function serializeBlockLink(el: HTMLElement, ctx: SerializeContext): string { + const href = el.getAttribute("href") || ""; + const text = el.textContent?.trim() || ""; + if (!href) { + return ctx.indent + text + "\n\n"; + } + return ctx.indent + formatLink(text, href) + "\n\n"; +} + +/** + * Render a link, mirroring the remark-stringify behavior from + * TypeCellOS/BlockNote#2661: when the link label equals the URL (or is + * empty), emit the bare URL so that pasting the link into another input + * produces a valid href instead of ``-autolink brackets or redundant + * `[url](url)` markup. Otherwise emit `[text](url)` with the URL escaped so + * a `)` inside the URL does not prematurely close the destination. + */ +function formatLink(text: string, href: string): string { + if (!text || text === href) { + return href; + } + return `[${text}](${escapeLinkDestination(href)})`; +} + +function escapeLinkDestination(url: string): string { + return url.replace(/[\\()]/g, "\\$&"); +} + +function serializeDetails(el: HTMLElement, ctx: SerializeContext): string { + // Toggle heading or toggle list item + const summary = el.querySelector("summary"); + if (!summary) { + return serializeChildren(el, ctx); + } + + // Check if summary contains a heading + const heading = summary.querySelector("h1, h2, h3, h4, h5, h6"); + if (heading) { + let result = serializeHeading(heading as HTMLElement, ctx); + // Also serialize non-summary children of details + for (const child of Array.from(el.children)) { + if (child !== summary) { + result += serializeNode(child, ctx); + } + } + return result; + } + + // Otherwise serialize the summary content + return serializeChildren(summary, ctx); +} + +// ─── Inline Content Serializer ─────────────────────────────────────────────── + +function serializeInlineContent(el: Element): string { + let result = ""; + + for (const child of Array.from(el.childNodes)) { + if (child.nodeType === 3 /* Node.TEXT_NODE */) { + result += child.textContent || ""; + } else if (child.nodeType === 1 /* Node.ELEMENT_NODE */) { + const childEl = child as HTMLElement; + const tag = childEl.tagName.toLowerCase(); + + switch (tag) { + case "strong": + case "b": { + const inner = serializeInlineContent(childEl); + const { content, trailing } = extractTrailingWhitespace(inner); + if (content) { + result += `**${content}**${trailing}`; + } else { + // All whitespace — just output it without emphasis + result += trailing; + } + break; + } + case "em": + case "i": { + const inner = serializeInlineContent(childEl); + const { content, trailing } = extractTrailingWhitespace(inner); + if (content) { + result += `*${content}*${trailing}`; + } else { + result += trailing; + } + break; + } + case "s": + case "del": + result += `~~${serializeInlineContent(childEl)}~~`; + break; + case "code": { + const text = childEl.textContent || ""; + const longestRun = Math.max( + 0, + ...(text.match(/`+/g) ?? []).map((run) => run.length), + ); + const fence = "`".repeat(longestRun + 1); + const needsPadding = text.startsWith("`") || text.endsWith("`"); + result += fence + (needsPadding ? ` ${text} ` : text) + fence; + break; + } + case "u": + // No markdown equivalent — strip the tag, keep content + result += serializeInlineContent(childEl); + break; + case "a": { + const href = childEl.getAttribute("href") || ""; + const text = serializeInlineContent(childEl); + result += formatLink(text, href); + break; + } + case "br": + result += "\\\n"; + break; + case "span": + // Color spans, etc. — strip the tag, keep content + result += serializeInlineContent(childEl); + break; + case "img": { + const src = childEl.getAttribute("src") || ""; + const alt = childEl.getAttribute("alt") || ""; + result += `![${alt}](${src})`; + break; + } + case "video": { + const src = + childEl.getAttribute("src") || + childEl.getAttribute("data-url") || + ""; + const name = + childEl.getAttribute("data-name") || + childEl.getAttribute("title") || + ""; + result += `![${name}](${src})`; + break; + } + case "p": + // Paragraph inside inline context (e.g., table cell) + result += serializeInlineContent(childEl); + break; + case "input": + // Checkbox in task list — handled at block level + break; + default: + result += serializeInlineContent(childEl); + break; + } + } + } + + return result; +} + +/** + * Extract trailing whitespace from emphasis content. + * Moves trailing spaces outside the emphasis delimiters to produce valid markdown. + * E.g., `Bold ` → `**Bold** ` instead of `**Bold **`. + */ +function extractTrailingWhitespace(text: string): { + content: string; + trailing: string; +} { + const match = text.match(/^(.*?)(\s*)$/); + if (match) { + return { content: match[1], trailing: match[2] }; + } + return { content: text, trailing: "" }; +} + +/** + * Escape leading character after emphasis if it could break parsing. + * For example, "Heading" after "**Bold **" — the 'H' should be escaped + * if the trailing space was escaped. + */ + +/** + * Trim leading/trailing hard breaks from inline content. + * Matches remark behavior where
at start/end of paragraph is dropped. + */ +function trimHardBreaks(content: string): string { + // Remove leading hard breaks + let result = content.replace(/^(\\\n)+/, ""); + // Remove trailing hard breaks produced by `
` + result = result.replace(/(\\\n)+$/, ""); + return result; +} diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.ts b/packages/core/src/api/exporters/markdown/markdownExporter.ts index 23aad8db7c..2f73616dc0 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.ts @@ -1,9 +1,4 @@ import { Schema } from "prosemirror-model"; -import rehypeParse from "rehype-parse"; -import rehypeRemark from "rehype-remark"; -import remarkGfm from "remark-gfm"; -import remarkStringify from "remark-stringify"; -import { unified } from "unified"; import { PartialBlock } from "../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; @@ -13,25 +8,11 @@ import { StyleSchema, } from "../../../schema/index.js"; import { createExternalHTMLExporter } from "../html/externalHTMLExporter.js"; -import { removeUnderlines } from "./util/removeUnderlinesRehypePlugin.js"; -import { addSpacesToCheckboxes } from "./util/addSpacesToCheckboxesRehypePlugin.js"; -import { convertVideoToMarkdown } from "./util/convertVideoToMarkdownRehypePlugin.js"; +import { htmlToMarkdown } from "./htmlToMarkdown.js"; // Needs to be sync because it's used in drag handler event (SideMenuPlugin) export function cleanHTMLToMarkdown(cleanHTMLString: string) { - const markdownString = unified() - .use(rehypeParse, { fragment: true }) - .use(convertVideoToMarkdown) - .use(removeUnderlines) - .use(addSpacesToCheckboxes) - .use(rehypeRemark) - .use(remarkGfm) - .use(remarkStringify, { - handlers: { text: (node) => node.value }, - }) - .processSync(cleanHTMLString); - - return markdownString.value as string; + return htmlToMarkdown(cleanHTMLString); } export function blocksToMarkdown< diff --git a/packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts b/packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts deleted file mode 100644 index 7c03eb9a64..0000000000 --- a/packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Element as HASTElement, Parent as HASTParent } from "hast"; -import { fromDom } from "hast-util-from-dom"; - -/** - * Rehype plugin which adds a space after each checkbox input element. This is - * because remark doesn't add any spaces between the checkbox input and the text - * itself, but these are needed for correct Markdown syntax. - */ -export function addSpacesToCheckboxes() { - const helper = (tree: HASTParent) => { - if (tree.children && "length" in tree.children && tree.children.length) { - for (let i = tree.children.length - 1; i >= 0; i--) { - const child = tree.children[i]; - const nextChild = - i + 1 < tree.children.length ? tree.children[i + 1] : undefined; - - // Checks for paragraph element after checkbox input element. - if ( - child.type === "element" && - child.tagName === "input" && - child.properties?.type === "checkbox" && - nextChild?.type === "element" && - nextChild.tagName === "p" - ) { - // Converts paragraph to span, otherwise remark will think it needs to - // be on a new line. - nextChild.tagName = "span"; - // Adds a space after the checkbox input element. - nextChild.children.splice( - 0, - 0, - fromDom(document.createTextNode(" ")) as HASTElement, - ); - } else { - helper(child as HASTParent); - } - } - } - }; - - return helper; -} diff --git a/packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts b/packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts deleted file mode 100644 index a7de2e3442..0000000000 --- a/packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Parent as HASTParent } from "hast"; -import { visit } from "unist-util-visit"; - -// Originally, rehypeParse parses videos as links, which is incorrect. -export function convertVideoToMarkdown() { - return (tree: HASTParent) => { - visit(tree, "element", (node, index, parent) => { - if (parent && node.tagName === "video") { - const src = node.properties?.src || node.properties?.["data-url"] || ""; - const name = - node.properties?.title || node.properties?.["data-name"] || ""; - parent.children[index!] = { - type: "text", - value: `![${name}](${src})`, - }; - } - }); - }; -} diff --git a/packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts b/packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts deleted file mode 100644 index 5b455d1b53..0000000000 --- a/packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Element as HASTElement, Parent as HASTParent } from "hast"; - -/** - * Rehype plugin which removes tags. Used to remove underlines before converting HTML to markdown, as Markdown - * doesn't support underlines. - */ -export function removeUnderlines() { - const removeUnderlinesHelper = (tree: HASTParent) => { - let numChildElements = tree.children.length; - - for (let i = 0; i < numChildElements; i++) { - const node = tree.children[i]; - - if (node.type === "element") { - // Recursively removes underlines from child elements. - removeUnderlinesHelper(node); - - if ((node as HASTElement).tagName === "u") { - // Lifts child nodes outside underline element, deletes the underline element, and updates current index & - // the number of child elements. - if (node.children.length > 0) { - tree.children.splice(i, 1, ...node.children); - - const numElementsAdded = node.children.length - 1; - numChildElements += numElementsAdded; - i += numElementsAdded; - } else { - tree.children.splice(i, 1); - - numChildElements--; - i--; - } - } - } - } - }; - - return removeUnderlinesHelper; -} diff --git a/packages/core/src/api/getBlocksChangedByTransaction.test.ts b/packages/core/src/api/getBlocksChangedByTransaction.test.ts index 03a3b464e8..c48d46f7b4 100644 --- a/packages/core/src/api/getBlocksChangedByTransaction.test.ts +++ b/packages/core/src/api/getBlocksChangedByTransaction.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeEach } from "vitest"; +import { describe, expect, it, beforeEach } from "vite-plus/test"; import { setupTestEnv } from "./blockManipulation/setupTestEnv.js"; import { getBlocksChangedByTransaction } from "./getBlocksChangedByTransaction.js"; diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 206ff8d9fd..d970227a49 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -366,8 +366,9 @@ export function blockToNode( groupNode ? [contentNode, groupNode] : contentNode, ); } else if (schema.nodes[block.type].isInGroup("bnBlock")) { - // this is a bnBlock node like Column or ColumnList that directly translates to a prosemirror node - return schema.nodes[block.type].createChecked( + // `create` (not `createChecked`) so partial container blocks pass through; + // callers that mutate the doc validate via `node.check()` before inserting. + return schema.nodes[block.type].create( { id: id, ...block.props, diff --git a/packages/core/src/api/parsers/html/parseHTML.ts b/packages/core/src/api/parsers/html/parseHTML.ts index 43f3dc4559..16e03f883a 100644 --- a/packages/core/src/api/parsers/html/parseHTML.ts +++ b/packages/core/src/api/parsers/html/parseHTML.ts @@ -8,6 +8,7 @@ import { import { Block } from "../../../blocks/defaultBlocks.js"; import { nodeToBlock } from "../../nodeConversions/nodeToBlock.js"; import { nestedListsToBlockNoteStructure } from "./util/nestedLists.js"; +import { preprocessHTMLWhitespace } from "./util/normalizeWhitespace.js"; export function HTMLToBlocks< BSchema extends BlockSchema, @@ -15,6 +16,7 @@ export function HTMLToBlocks< S extends StyleSchema, >(html: string, pmSchema: Schema): Block[] { const htmlNode = nestedListsToBlockNoteStructure(html); + preprocessHTMLWhitespace(htmlNode); const parser = DOMParser.fromSchema(pmSchema); // Other approach might be to use diff --git a/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap b/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap index 68c0a1c817..1db488255b 100644 --- a/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap +++ b/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap @@ -1,129 +1,144 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Lift nested lists > Lifts multiple bullet lists 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
      -
    • Nested Bullet List Item 3
    • -
    • Nested Bullet List Item 4
    • -
    -
    -
    -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
      +
    • + Nested Bullet List Item 3 +
    • +
    • + Nested Bullet List Item 4 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts multiple bullet lists with content in between 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
    -
    -
    -
  • In between content
  • -
    -
      -
    • Nested Bullet List Item 3
    • -
    • Nested Bullet List Item 4
    • -
    -
    -
    -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
  • + In between content +
    • +
    • + Nested Bullet List Item 3 +
    • +
    • + Nested Bullet List Item 4 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested bullet lists 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
    -
    -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested bullet lists with content after nested list 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
    -
    -
  • More content in list item 1
  • -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
  • + More content in list item 1 +
  • +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested bullet lists without li 1`] = ` -" -
    Bullet List Item 1 -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
  • Bullet List Item 2
  • -
-" +"
    + Bullet List Item 1 +
      +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested mixed lists 1`] = ` -" -
    -
    -
  1. Numbered List Item 1
  2. -
    -
      -
    • Bullet List Item 1
    • -
    • Bullet List Item 2
    • -
    -
    -
    -
  3. Numbered List Item 2
  4. -
-" +"
    +
  1. + Numbered List Item 1 +
    • +
    • + Bullet List Item 1 +
    • +
    • + Bullet List Item 2 +
    • +
    +
  2. + Numbered List Item 2 +
  3. +
" `; exports[`Lift nested lists > Lifts nested numbered lists 1`] = ` -" -
    -
    -
  1. Numbered List Item 1
  2. -
    -
      -
    1. Nested Numbered List Item 1
    2. -
    3. Nested Numbered List Item 2
    4. -
    -
    -
    -
  3. Numbered List Item 2
  4. -
-" +"
    +
  1. + Numbered List Item 1 +
    1. +
    2. + Nested Numbered List Item 1 +
    3. +
    4. + Nested Numbered List Item 2 +
    5. +
    +
  2. + Numbered List Item 2 +
  3. +
" `; diff --git a/packages/core/src/api/parsers/html/util/nestedLists.test.ts b/packages/core/src/api/parsers/html/util/nestedLists.test.ts index 03fadebefe..38ee081287 100644 --- a/packages/core/src/api/parsers/html/util/nestedLists.test.ts +++ b/packages/core/src/api/parsers/html/util/nestedLists.test.ts @@ -1,20 +1,9 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { nestedListsToBlockNoteStructure } from "./nestedLists.js"; -import { unified } from "unified"; -import rehypeParse from "rehype-parse"; -import rehypeFormat from "rehype-format"; -import rehypeStringify from "rehype-stringify"; async function testHTML(html: string) { const htmlNode = nestedListsToBlockNoteStructure(html); - - const pretty = await unified() - .use(rehypeParse, { fragment: true }) - .use(rehypeFormat) - .use(rehypeStringify) - .process(htmlNode.innerHTML); - - expect(pretty.value).toMatchSnapshot(); + expect(htmlNode.innerHTML).toMatchSnapshot(); } describe("Lift nested lists", () => { diff --git a/packages/core/src/api/parsers/html/util/normalizeWhitespace.ts b/packages/core/src/api/parsers/html/util/normalizeWhitespace.ts new file mode 100644 index 0000000000..9cacd86e15 --- /dev/null +++ b/packages/core/src/api/parsers/html/util/normalizeWhitespace.ts @@ -0,0 +1,87 @@ +/** + * Checks if the given HTML element contains markers indicating it was + * generated by Notion. Notion uses `\n` in text nodes to represent hard + * breaks, which is non-standard but intentional. + * + * Detected by the `` comment that Notion places + * on the clipboard. + */ +function isNotionHTML(element: HTMLElement): boolean { + const walker = element.ownerDocument.createTreeWalker( + element, + // NodeFilter.SHOW_COMMENT + 128, + ); + + let node: Node | null; + while ((node = walker.nextNode())) { + if (/^\s*notionvc:/.test(node.nodeValue || "")) { + return true; + } + } + + return false; +} + +/** + * Normalizes whitespace in text nodes by collapsing runs of whitespace + * (including newlines) to single spaces, matching CSS white-space:normal + * behavior. + * + * This is needed because ProseMirror's DOMParser, when `linebreakReplacement` + * is set in the schema (as BlockNote does for hard breaks), converts `\n` + * characters in text nodes to hard break nodes instead of collapsing them. + * This causes HTML source line wrapping (e.g. from MS Word) to create + * visible line breaks in the editor. + * + * Skipped for sources like Notion that intentionally use `\n` in text nodes + * to represent hard breaks instead of `
` tags. + * + * Skips `
` and `` elements where whitespace should be preserved.
+ */
+function normalizeTextNodeWhitespace(element: HTMLElement) {
+  const preserveWSTags = new Set(["PRE", "CODE"]);
+  const walker = element.ownerDocument.createTreeWalker(
+    element,
+    // NodeFilter.SHOW_TEXT
+    4,
+    {
+      acceptNode(node) {
+        // Skip text nodes inside pre/code elements
+        let parent = node.parentElement;
+        while (parent && parent !== element) {
+          if (preserveWSTags.has(parent.tagName)) {
+            // NodeFilter.FILTER_REJECT
+            return 2;
+          }
+          parent = parent.parentElement;
+        }
+        // NodeFilter.FILTER_ACCEPT
+        return 1;
+      },
+    },
+  );
+
+  const textNodes: Text[] = [];
+  let node: Node | null;
+  while ((node = walker.nextNode())) {
+    textNodes.push(node as Text);
+  }
+
+  for (const textNode of textNodes) {
+    if (textNode.nodeValue && /[\r\n]/.test(textNode.nodeValue)) {
+      textNode.nodeValue = textNode.nodeValue.replace(/[ \t\r\n\f]+/g, " ");
+    }
+  }
+}
+
+/**
+ * Normalizes whitespace in HTML text nodes to match standard CSS
+ * white-space:normal behavior. Skipped for Notion HTML which intentionally
+ * uses `\n` for hard breaks.
+ */
+export function preprocessHTMLWhitespace(element: HTMLElement) {
+  if (!isNotionHTML(element)) {
+    normalizeTextNodeWhitespace(element);
+  }
+}
diff --git a/packages/core/src/api/parsers/markdown/detectMarkdown.test.ts b/packages/core/src/api/parsers/markdown/detectMarkdown.test.ts
index 45c416bacb..9d8a101b79 100644
--- a/packages/core/src/api/parsers/markdown/detectMarkdown.test.ts
+++ b/packages/core/src/api/parsers/markdown/detectMarkdown.test.ts
@@ -1,4 +1,4 @@
-import { describe, expect, it } from "vitest";
+import { describe, expect, it } from "vite-plus/test";
 import { isMarkdown } from "./detectMarkdown.js";
 
 describe("isMarkdown", () => {
diff --git a/packages/core/src/api/parsers/markdown/markdownToHtml.ts b/packages/core/src/api/parsers/markdown/markdownToHtml.ts
new file mode 100644
index 0000000000..1b3d2e1c11
--- /dev/null
+++ b/packages/core/src/api/parsers/markdown/markdownToHtml.ts
@@ -0,0 +1,1340 @@
+import { isVideoUrl } from "../../../util/string.js";
+
+/**
+ * Custom markdown-to-HTML converter for BlockNote.
+ * Replaces the unified/remark/rehype pipeline with a direct, minimal implementation
+ * that handles exactly the markdown features BlockNote needs.
+ */
+
+// ─── HTML Escaping ───────────────────────────────────────────────────────────
+
+function escapeHtml(str: string): string {
+  return str
+    .replace(/&/g, "&")
+    .replace(//g, ">")
+    .replace(/"/g, """);
+}
+
+// ─── Helpers ─────────────────────────────────────────────────────────────────
+
+function isAlphanumeric(char: string | undefined): boolean {
+  if (!char) {
+    return false;
+  }
+  return /\w/.test(char);
+}
+
+/**
+ * Returns true when an underscore delimiter at position `i` is "intraword",
+ * meaning the characters on both sides are alphanumeric (e.g. `snake_case`).
+ * In that case the underscore should NOT be treated as emphasis per CommonMark.
+ */
+function isIntraword(text: string, i: number, delimLen: number): boolean {
+  const before = i > 0 ? text[i - 1] : undefined;
+  const after = i + delimLen < text.length ? text[i + delimLen] : undefined;
+  return isAlphanumeric(before) && isAlphanumeric(after);
+}
+
+// ─── Inline Parser ───────────────────────────────────────────────────────────
+
+type InlineTokenizer = (
+  text: string,
+  i: number,
+) => { html: string; end: number } | null;
+
+function tryBackslashEscape(
+  text: string,
+  i: number,
+): { html: string; end: number } | null {
+  if (text[i] !== "\\" || i + 1 >= text.length) {
+    return null;
+  }
+  const next = text[i + 1];
+  // Hard line break: backslash at end of line
+  if (next === "\n") {
+    return { html: "
\n", end: i + 2 }; + } + // Escapable characters + if ("\\`*_{}[]()#+-.!~|>".includes(next)) { + return { html: escapeHtml(next), end: i + 2 }; + } + return null; +} + +function tryInlineCode( + text: string, + i: number, +): { html: string; end: number } | null { + if (text[i] !== "`") { + return null; + } + return parseInlineCode(text, i); +} + +function tryImage( + text: string, + i: number, +): { html: string; end: number } | null { + if (text[i] !== "!" || text[i + 1] !== "[") { + return null; + } + return parseImage(text, i); +} + +function tryLink( + text: string, + i: number, +): { html: string; end: number } | null { + if (text[i] !== "[") { + return null; + } + return parseLink(text, i); +} + +function tryStrikethrough( + text: string, + i: number, +): { html: string; end: number } | null { + if (text[i] !== "~" || text[i + 1] !== "~") { + return null; + } + return parseDelimited(text, i, "~~", "", ""); +} + +function tryBoldItalic( + text: string, + i: number, +): { html: string; end: number } | null { + if ( + (text[i] === "*" && text[i + 1] === "*" && text[i + 2] === "*") || + (text[i] === "_" && + text[i + 1] === "_" && + text[i + 2] === "_" && + !isIntraword(text, i, 3)) + ) { + const delimiter = text.substring(i, i + 3); + return parseDelimited(text, i, delimiter, "", ""); + } + return null; +} + +function tryBold( + text: string, + i: number, +): { html: string; end: number } | null { + if ( + (text[i] === "*" && text[i + 1] === "*") || + (text[i] === "_" && text[i + 1] === "_" && !isIntraword(text, i, 2)) + ) { + const delimiter = text.substring(i, i + 2); + return parseDelimited(text, i, delimiter, "", ""); + } + return null; +} + +function tryItalic( + text: string, + i: number, +): { html: string; end: number } | null { + if (text[i] === "*" || (text[i] === "_" && !isIntraword(text, i, 1))) { + return parseDelimited(text, i, text[i], "", ""); + } + return null; +} + +function trySoftBreak( + text: string, + i: number, +): { html: string; end: number } | null { + if (text[i] === "\n") { + return { html: "
\n", end: i + 1 }; + } + return null; +} + +// Inline raw HTML: pass through tags, comments, CDATA, processing +// instructions, and declarations verbatim so authors can mix HTML into +// markdown (e.g. `text foo more`). Anything that doesn't match +// these shapes falls through and gets HTML-escaped as plain text. +const INLINE_HTML_TAG_RE = + /^<\/?[a-zA-Z][a-zA-Z0-9-]*(?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*\/?>/; +const HTML_COMMENT_RE = /^/; +const HTML_CDATA_RE = /^/; +const HTML_PI_RE = /^<\?[\s\S]*?\?>/; +const HTML_DECL_RE = /^/; + +function tryInlineHtml( + text: string, + i: number, +): { html: string; end: number } | null { + if (text[i] !== "<") { + return null; + } + const rest = text.substring(i); + for (const re of [ + HTML_COMMENT_RE, + HTML_CDATA_RE, + HTML_PI_RE, + HTML_DECL_RE, + INLINE_HTML_TAG_RE, + ]) { + const m = rest.match(re); + if (m) { + return { html: m[0], end: i + m[0].length }; + } + } + return null; +} + +/** Characters that can start an inline syntax token. */ +const SPECIAL_CHARS = new Set("\\`![~*_\n<"); + +/** + * Ordered array of inline tokenizers, tried in priority order. + * The first match wins. + */ +const inlineTokenizers: InlineTokenizer[] = [ + tryBackslashEscape, + tryInlineCode, + tryImage, + tryLink, + tryStrikethrough, + tryBoldItalic, // *** / ___ + tryBold, // ** / __ + tryItalic, // * / _ + tryInlineHtml, + trySoftBreak, +]; + +/** + * Parse inline markdown syntax and return HTML. + * Handles: bold, italic, bold+italic, strikethrough, inline code, + * links, images (with video detection), hard line breaks, backslash escapes. + */ +function parseInline(text: string): string { + let result = ""; + let i = 0; + + while (i < text.length) { + // Hard line break: 2+ trailing spaces immediately before a newline. + // (The other hard-break form, backslash + newline, is handled by + // tryBackslashEscape.) Strip the trailing spaces from the accumulated + // result before emitting the
. + if ( + text[i] === "\n" && + i >= 2 && + text[i - 1] === " " && + text[i - 2] === " " + ) { + result = result.replace(/ +$/, ""); + result += "
\n"; + i++; + continue; + } + + // Try each tokenizer in priority order + let matched = false; + if (SPECIAL_CHARS.has(text[i])) { + for (const tokenizer of inlineTokenizers) { + const r = tokenizer(text, i); + if (r) { + result += r.html; + i = r.end; + matched = true; + break; + } + } + } + + if (!matched) { + // Batch consecutive plain-text characters and escape once + const runStart = i; + i++; + while (i < text.length && !SPECIAL_CHARS.has(text[i])) { + i++; + } + result += escapeHtml(text.substring(runStart, i)); + } + } + + return result; +} + +function parseInlineCode( + text: string, + start: number, +): { html: string; end: number } | null { + // Count opening backticks + let openCount = 0; + let i = start; + while (i < text.length && text[i] === "`") { + openCount++; + i++; + } + + // Find matching closing backticks + let j = i; + while (j < text.length) { + if (text[j] === "`") { + let closeCount = 0; + const closeStart = j; + while (j < text.length && text[j] === "`") { + closeCount++; + j++; + } + if (closeCount === openCount) { + let code = text.substring(i, closeStart); + // Per CommonMark: line endings inside a code span are converted to + // single spaces, then if the result starts AND ends with a space and + // is not all-spaces, one leading + trailing space is stripped (so + // `` ` `foo` ` `` is ``foo``). + code = code.replace(/\n/g, " "); + if ( + code.length >= 2 && + code[0] === " " && + code[code.length - 1] === " " && + /[^ ]/.test(code) + ) { + code = code.substring(1, code.length - 1); + } + return { + html: `${escapeHtml(code)}`, + end: j, + }; + } + } else { + j++; + } + } + return null; +} + +function parseImage( + text: string, + start: number, +): { html: string; end: number } | null { + // ![alt](url) or ![alt](url "title") + // Use balanced bracket matching to handle nested/escaped brackets in alt text + const altEnd = findClosingBracket(text, start + 1); + if (altEnd === -1) { + return null; + } + const altStart = start + 2; // after ![ + + if (text[altEnd + 1] !== "(") { + return null; + } + + const urlStart = altEnd + 2; + const parenEnd = findClosingParen(text, urlStart - 1); + if (parenEnd === -1) { + return null; + } + + const alt = text.substring(altStart, altEnd); + const { url, title } = parseDestinationAndTitle( + text.substring(urlStart, parenEnd), + ); + + if (isVideoUrl(url)) { + // Use the alt text as the video's display name (falling back to the + // title) so a video link written with the standard `![name](url)` form + // round-trips into BlockNote's video block. Captioned videos go through + // raw `
` HTML instead, see htmlToMarkdown.serializeMediaFigure. + const name = alt || title; + return { + html: ``, + end: parenEnd + 1, + }; + } + + const titleAttr = title !== undefined ? ` title="${escapeHtml(title)}"` : ""; + return { + html: `${escapeHtml(alt)}`, + end: parenEnd + 1, + }; +} + +function parseLink( + text: string, + start: number, +): { html: string; end: number } | null { + // [text](url) + const textStart = start + 1; + const textEnd = findClosingBracket(text, start); + if (textEnd === -1) { + return null; + } + + if (text[textEnd + 1] !== "(") { + return null; + } + + const urlStart = textEnd + 2; + const parenEnd = findClosingParen(text, textEnd + 1); + if (parenEnd === -1) { + return null; + } + + const linkText = text.substring(textStart, textEnd); + const { url, title } = parseDestinationAndTitle( + text.substring(urlStart, parenEnd), + ); + + const titleAttr = title !== undefined ? ` title="${escapeHtml(title)}"` : ""; + return { + html: `${parseInline(linkText)}`, + end: parenEnd + 1, + }; +} + +function findClosingBracket(text: string, openPos: number): number { + let depth = 0; + for (let i = openPos; i < text.length; i++) { + if (text[i] === "\\" && i + 1 < text.length) { + i++; // skip escaped + continue; + } + if (text[i] === "[") { + depth++; + } + if (text[i] === "]") { + depth--; + if (depth === 0) { + return i; + } + } + } + return -1; +} + +function findClosingParen(text: string, openPos: number): number { + let depth = 0; + for (let i = openPos; i < text.length; i++) { + if (text[i] === "\\" && i + 1 < text.length) { + i++; + continue; + } + if (text[i] === "(") { + depth++; + } + if (text[i] === ")") { + depth--; + if (depth === 0) { + return i; + } + } + } + return -1; +} + +/** + * Parse the inside of `(...)` from a link/image (the URL and optional title). + * Handles three URL forms: + * - bare: `/uri` or `/uri "title"` + * - angle-bracket: `` or ` "title"` (brackets are stripped) + * And three title-quote forms: `"..."`, `'...'`, `(...)`. + */ +function parseDestinationAndTitle(raw: string): { + url: string; + title?: string; +} { + raw = raw.trim(); + let url: string; + let rest: string; + + if (raw.startsWith("<")) { + const close = raw.indexOf(">"); + if (close === -1) { + // Unmatched `<` — treat the whole thing as the URL minus the `<`. + url = raw.substring(1); + rest = ""; + } else { + url = raw.substring(1, close); + rest = raw.substring(close + 1).trim(); + } + } else { + // Split at first unescaped whitespace. + let split = raw.length; + for (let i = 0; i < raw.length; i++) { + if (raw[i] === "\\" && i + 1 < raw.length) { + i++; + continue; + } + if (raw[i] === " " || raw[i] === "\t" || raw[i] === "\n") { + split = i; + break; + } + } + url = raw.substring(0, split); + rest = raw.substring(split).trim(); + } + + let title: string | undefined; + if (rest.length > 0) { + const titleMatch = rest.match(/^"([^"]*)"$|^'([^']*)'$|^\(([^)]*)\)$/); + if (titleMatch) { + title = titleMatch[1] ?? titleMatch[2] ?? titleMatch[3]; + } + } + + return { url, title }; +} + +function parseDelimited( + text: string, + start: number, + delimiter: string, + openTag: string, + closeTag: string, +): { html: string; end: number } | null { + const len = delimiter.length; + const afterOpen = start + len; + + if (afterOpen >= text.length) { + return null; + } + + // Opening delimiter must not be followed by whitespace + if (text[afterOpen] === " " || text[afterOpen] === "\t") { + return null; + } + + // Find closing delimiter + let j = afterOpen; + while (j < text.length) { + // Skip escaped characters + if (text[j] === "\\" && j + 1 < text.length) { + j += 2; + continue; + } + + if (text.substring(j, j + len) === delimiter) { + // Closing delimiter must not be preceded by whitespace + if (text[j - 1] === " " || text[j - 1] === "\t") { + j++; + continue; + } + + // For single-char delimiters, don't accept closer if it's part of a + // multi-char run (e.g., don't treat the * in ** as italic closer) + if ( + len === 1 && + ((j > 0 && + text[j - 1] === delimiter[0] && + !(j >= 2 && text[j - 2] === "\\")) || + (j + len < text.length && text[j + len] === delimiter[0])) + ) { + j++; + continue; + } + + const inner = text.substring(afterOpen, j); + if (inner.length === 0) { + j++; + continue; + } + + return { + html: openTag + parseInline(inner) + closeTag, + end: j + len, + }; + } + j++; + } + + return null; +} + +// ─── Block-Level Types ─────────────────────────────────────────────────────── + +interface BlockToken { + type: string; +} + +interface HeadingToken extends BlockToken { + type: "heading"; + level: number; + content: string; +} + +interface ParagraphToken extends BlockToken { + type: "paragraph"; + content: string; +} + +interface CodeBlockToken extends BlockToken { + type: "codeBlock"; + language: string; + code: string; +} + +interface BlockquoteToken extends BlockToken { + type: "blockquote"; + content: string; +} + +interface HorizontalRuleToken extends BlockToken { + type: "hr"; +} + +interface ListItemToken extends BlockToken { + type: "listItem"; + listType: "bullet" | "ordered" | "task"; + indent: number; + content: string; + start?: number; // for ordered lists + checked?: boolean; // for task lists + childContent?: string; // recursively parsed content within this item +} + +interface TableToken extends BlockToken { + type: "table"; + headers: string[]; + rows: string[][]; + alignments: ("left" | "center" | "right" | null)[]; +} + +interface RawHtmlToken extends BlockToken { + type: "rawHtml"; + content: string; +} + +type Token = + | HeadingToken + | ParagraphToken + | CodeBlockToken + | BlockquoteToken + | HorizontalRuleToken + | ListItemToken + | TableToken + | RawHtmlToken; + +/** + * HTML block-level tag names (from the CommonMark type-6 list, plus `audio` + * which BlockNote serializes as raw HTML since markdown has no shorthand + * for it). When a line starts with `<` followed by one of these tag names, + * the run of non-blank lines is emitted verbatim as raw HTML rather than + * wrapped in a paragraph. + */ +const HTML_BLOCK_TAGS = new Set([ + "address", + "article", + "aside", + "audio", + "base", + "basefont", + "blockquote", + "body", + "caption", + "center", + "col", + "colgroup", + "dd", + "details", + "dialog", + "dir", + "div", + "dl", + "dt", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hr", + "html", + "iframe", + "legend", + "li", + "link", + "main", + "menu", + "menuitem", + "nav", + "noframes", + "ol", + "optgroup", + "option", + "p", + "param", + "section", + "source", + "summary", + "table", + "tbody", + "td", + "tfoot", + "th", + "thead", + "title", + "tr", + "track", + "ul", +]); + +function isHtmlBlockStart(line: string): boolean { + // `, ``, ``, or ``. + // Lines are emitted verbatim until the next blank line. + if (isHtmlBlockStart(line)) { + const htmlLines: string[] = []; + while (i < lines.length && lines[i].trim() !== "") { + htmlLines.push(lines[i]); + i++; + } + tokens.push({ + type: "rawHtml", + content: htmlLines.join("\n"), + }); + prevLineWasBlank = false; + continue; + } + + // Paragraph (default) + const paraLines: string[] = [line]; + i++; + while (i < lines.length) { + const nextLine = lines[i]; + // Stop paragraph on blank line + if (nextLine.trim() === "") { + break; + } + // Stop on block-level element + if (/^(#{1,6})\s/.test(nextLine)) { + break; + } + if (/^(`{3,}|~{3,})/.test(nextLine)) { + break; + } + if (/^\s{0,3}>/.test(nextLine)) { + break; + } + if (/^(\s{0,3})([-*_])\s*(\2\s*){2,}$/.test(nextLine)) { + break; + } + if (/^\s*([-*+]|\d+[.)])\s+/.test(nextLine)) { + break; + } + if (/^\s*\|(.+\|)+\s*$/.test(nextLine)) { + break; + } + if (isHtmlBlockStart(nextLine)) { + break; + } + // Check if next-next line is setext marker + if ( + i + 1 < lines.length && + /^[=-]+\s*$/.test(lines[i + 1]) && + nextLine.trim().length > 0 + ) { + break; + } + paraLines.push(nextLine); + i++; + } + // CommonMark allows up to 3 leading spaces of indent on paragraph lines. + // Also strip trailing whitespace from the final line so a trailing + // hard-break sequence (` \n` at end of paragraph) doesn't leak as + // literal trailing spaces in the rendered output. + tokens.push({ + type: "paragraph", + content: paraLines + .map((l) => l.replace(/^ {1,3}/, "")) + .join("\n") + .replace(/[ \t]+$/, ""), + }); + prevLineWasBlank = false; + } + + return tokens; +} + +function tryParseTable( + lines: string[], + start: number, +): { token: TableToken; nextLine: number } | null { + // A table needs at least a header row and a separator row + if (start + 1 >= lines.length) { + return null; + } + + const headerLine = lines[start]; + const separatorLine = lines[start + 1]; + + // Check separator line format: | --- | --- | or --- | --- (outer pipes optional) + // Must contain at least one pipe and only dashes, colons, pipes, and whitespace + if ( + !separatorLine.includes("|") || + !/^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(separatorLine) + ) { + return null; + } + + // Check header line has at least one pipe (required to distinguish from plain text) + if (!headerLine.includes("|")) { + return null; + } + + const headers = parsePipeCells(headerLine); + const alignments = parseAlignments(separatorLine); + + const rows: string[][] = []; + let i = start + 2; + while (i < lines.length) { + const line = lines[i]; + if (!line.includes("|")) { + break; + } + rows.push(parsePipeCells(line)); + i++; + } + + return { + token: { + type: "table", + headers, + rows, + alignments, + }, + nextLine: i, + }; +} + +function parsePipeCells(line: string): string[] { + // Trim leading/trailing pipes and split + const trimmed = line.trim(); + const withoutOuterPipes = trimmed.startsWith("|") + ? trimmed.substring(1) + : trimmed; + const content = withoutOuterPipes.endsWith("|") + ? withoutOuterPipes.substring(0, withoutOuterPipes.length - 1) + : withoutOuterPipes; + + // Split by pipes, handling escaped pipes + const cells: string[] = []; + let current = ""; + for (let i = 0; i < content.length; i++) { + if ( + content[i] === "\\" && + i + 1 < content.length && + content[i + 1] === "|" + ) { + current += "|"; + i++; + } else if (content[i] === "|") { + cells.push(current.trim()); + current = ""; + } else { + current += content[i]; + } + } + cells.push(current.trim()); + + return cells; +} + +function parseAlignments( + separatorLine: string, +): ("left" | "center" | "right" | null)[] { + const cells = parsePipeCells(separatorLine); + return cells.map((cell) => { + const trimmed = cell.trim(); + const left = trimmed.startsWith(":"); + const right = trimmed.endsWith(":"); + if (left && right) { + return "center"; + } + if (right) { + return "right"; + } + if (left) { + return "left"; + } + return null; + }); +} + +// ─── HTML Emitter ──────────────────────────────────────────────────────────── + +function tokensToHtml(tokens: Token[]): string { + let html = ""; + let i = 0; + + while (i < tokens.length) { + const token = tokens[i]; + + switch (token.type) { + case "heading": { + const t = token as HeadingToken; + html += `${parseInline(t.content)}`; + i++; + break; + } + + case "paragraph": { + const t = token as ParagraphToken; + html += `

${parseInline(t.content)}

`; + i++; + break; + } + + case "codeBlock": { + const t = token as CodeBlockToken; + const langAttr = t.language + ? ` data-language="${escapeHtml(t.language)}"` + : ""; + html += `
${escapeHtml(t.code)}
`; + i++; + break; + } + + case "blockquote": { + const t = token as BlockquoteToken; + // Recursively parse blockquote content as markdown + const innerTokens = tokenize(t.content); + const innerHtml = tokensToHtml(innerTokens); + html += `
${innerHtml}
`; + i++; + break; + } + + case "hr": + html += `
`; + i++; + break; + + case "listItem": { + // Collect consecutive list items and build nested list structure + const listHtml = emitListItems(tokens, i); + html += listHtml.html; + i = listHtml.nextIndex; + break; + } + + case "table": { + const t = token as TableToken; + html += emitTable(t); + i++; + break; + } + + case "rawHtml": { + const t = token as RawHtmlToken; + html += t.content; + i++; + break; + } + + default: + i++; + } + } + + return html; +} + +function emitListItems( + tokens: Token[], + startIdx: number, +): { html: string; nextIndex: number } { + let html = ""; + let i = startIdx; + let currentListType: "bullet" | "ordered" | null = null; + + while (i < tokens.length && tokens[i].type === "listItem") { + const item = tokens[i] as ListItemToken; + const effectiveType = getEffectiveListType(item.listType); + + // Check if we need to switch list type + if (currentListType !== null && currentListType !== effectiveType) { + // Close current list, open new one + html += ``; + currentListType = null; + } + + // Open list if needed + if (currentListType === null) { + if (effectiveType === "ordered") { + const startAttr = + item.start !== undefined && item.start !== 1 + ? ` start="${item.start}"` + : ""; + html += ``; + } else { + html += `
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 c25f830ff4..ce97dbaaac 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 @@ -9,9 +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 new file mode 100644 index 0000000000..4376ebf7f1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html @@ -0,0 +1,90 @@ +
+
+
+
+

Document Title

+
+
+
+
+
+
+

Introduction paragraph.

+
+
+
+
+
+
+

Section 1

+
+
+
+
+
+
+

+ Text with + bold + and + a link + . +

+
+
+
+
+
+
+

First point

+
+
+
+
+
+
+

Second point

+
+
+
+
+
+
+
+
+
+
+
+
+
+
A notable quote
+
+
+
+
+
+
+
+ +
+
+          const x = 42;
+        
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html index 9974d8d975..d7802da3e3 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html @@ -9,7 +9,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -20,8 +20,8 @@

example

-

Caption

-
+
Caption
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html index 6553a5c4a8..c62486d27d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html @@ -9,7 +9,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -20,8 +20,8 @@

example

-

Caption

-
+
Caption
+
@@ -34,7 +34,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -45,8 +45,8 @@

example

-

Caption

-
+
Caption
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html index 47ae5b3bf9..1ec20af747 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html @@ -8,7 +8,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -19,8 +19,8 @@

-

Caption

-
+
Caption
+ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html index 9e4b427c62..cbc42aa3f9 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html @@ -4,15 +4,21 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html index 4cae02d67b..a896507f92 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html @@ -4,15 +4,21 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h1.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h1.html new file mode 100644 index 0000000000..3e3e513852 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h1.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 1

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h2.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h2.html new file mode 100644 index 0000000000..7ffe42afa3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h2.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 2

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h3.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h3.html new file mode 100644 index 0000000000..437867e0d9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h3.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 3

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h4.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h4.html new file mode 100644 index 0000000000..1ef4e627ff --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h4.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 4

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h5.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h5.html new file mode 100644 index 0000000000..f44690aa57 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h5.html @@ -0,0 +1,9 @@ +
+
+
+
+
Heading 5
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h6.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h6.html new file mode 100644 index 0000000000..5daca5fbdf --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h6.html @@ -0,0 +1,9 @@ +
+
+
+
+
Heading 6
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/styled.html new file mode 100644 index 0000000000..31df3416ba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/styled.html @@ -0,0 +1,12 @@ +
+
+
+
+

+ Bold + Heading +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/toggleable.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/toggleable.html new file mode 100644 index 0000000000..2982ce3673 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/toggleable.html @@ -0,0 +1,38 @@ +
+
+
+
+
+
+ +

Toggle Heading

+
+
+
+
+
+
+
+

Child content

+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html index c26a9764e1..a17b1d3982 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html @@ -15,9 +15,11 @@ BlockNote image + + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html index 586f875965..8700c93f57 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html @@ -10,15 +10,23 @@ data-preview-width="256" data-file-block="" > -
- example + example + +
-

Caption

-
+
Caption
+ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html index a36d9be3f1..b7d2064ce9 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html @@ -9,15 +9,23 @@ data-preview-width="256" data-file-block="" > -
- Caption + + +
-

Caption

-
+
Caption
+
@@ -30,15 +38,23 @@ data-preview-width="256" data-file-block="" > -
- Caption + + +
-

Caption

-
+
Caption
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html index 5f2ec1644c..44b706cebe 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html @@ -14,7 +14,15 @@ style="position: relative; width: 256px;" >
- example + example + +
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html index 9403217ea6..2a2bbd7a8d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html @@ -9,15 +9,23 @@ data-preview-width="256" data-file-block="" > -
- Caption + + +
-

Caption

-
+
Caption
+ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html index 3e1f5a6264..59d65f82ae 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html @@ -11,7 +11,7 @@ data-preview-width="256" data-file-block="" > -
+
@@ -22,8 +22,8 @@

example

-

Caption

-
+
Caption
+ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/urlOnly.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/urlOnly.html new file mode 100644 index 0000000000..6940729386 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/urlOnly.html @@ -0,0 +1,23 @@ +
+
+
+
+
+
+ + + +
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/withCaption.html new file mode 100644 index 0000000000..5844c98607 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/withCaption.html @@ -0,0 +1,31 @@ +
+
+
+
+
+
+ Example Image + + +
+
This is a caption
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html index 2408c611ac..9af317b375 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html @@ -4,14 +4,20 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html index 3daea90831..159dfce9ea 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html @@ -4,9 +4,12 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/plainUrl.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/plainUrl.html new file mode 100644 index 0000000000..db540213b4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/plainUrl.html @@ -0,0 +1,18 @@ +
+
+ +
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html index 2b9d4cb574..766681b45a 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html @@ -5,15 +5,21 @@

Web site

diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/urlWithParens.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/urlWithParens.html new file mode 100644 index 0000000000..ab632bd13f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/urlWithParens.html @@ -0,0 +1,18 @@ +
+
+
+
+

+ Example +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/withCode.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/withCode.html new file mode 100644 index 0000000000..45427ff9ad --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/withCode.html @@ -0,0 +1,21 @@ +
+
+
+
+

+ See the + docs + for + config +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/numberedListStart.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/numberedListStart.html new file mode 100644 index 0000000000..62b0458466 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/numberedListStart.html @@ -0,0 +1,25 @@ +
+
+
+
+

Item 5

+
+
+
+
+
+
+

Item 6

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/toggleWithChildren.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/toggleWithChildren.html new file mode 100644 index 0000000000..018c41520e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/toggleWithChildren.html @@ -0,0 +1,76 @@ +
+
+
+
+
+
+ +

Toggle List Item

+
+
+
+
+
+
+
+

Toggle Child 1

+
+
+
+
+
+
+

Toggle Child 2

+
+
+
+
+
+
+
+
+
+
+
+ +

Toggle Heading

+
+
+
+
+
+
+
+

Heading Child 1

+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/multiple.html new file mode 100644 index 0000000000..a5c65cdb68 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/multiple.html @@ -0,0 +1,23 @@ +
+
+
+
+

First paragraph

+
+
+
+
+
+
+

Second paragraph

+
+
+
+
+
+
+

Third paragraph

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/basic.html new file mode 100644 index 0000000000..278aafa3ec --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/basic.html @@ -0,0 +1,9 @@ +
+
+
+
+
This is a quote
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/multiple.html new file mode 100644 index 0000000000..99c7aa530e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/multiple.html @@ -0,0 +1,16 @@ +
+
+
+
+
First quote
+
+
+
+
+
+
+
Second quote
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/nested.html new file mode 100644 index 0000000000..da3b3d1215 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/nested.html @@ -0,0 +1,18 @@ +
+
+
+
+
Parent quote
+
+
+
+
+
+

Nested paragraph

+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/styled.html new file mode 100644 index 0000000000..244868c2c6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/styled.html @@ -0,0 +1,14 @@ +
+
+
+
+
+ Bold + and + italic + quote +
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/withLink.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/withLink.html new file mode 100644 index 0000000000..5cbacbdec1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/withLink.html @@ -0,0 +1,19 @@ +
+
+
+
+
+ Quote with + a link +
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/backgroundColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/backgroundColor.html new file mode 100644 index 0000000000..da1b939f67 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/backgroundColor.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Highlighted text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/bold.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/bold.html new file mode 100644 index 0000000000..1834b5c3ca --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/bold.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Bold text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/boldItalicStrike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/boldItalicStrike.html new file mode 100644 index 0000000000..f8baaa4507 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/boldItalicStrike.html @@ -0,0 +1,15 @@ +
+
+
+
+

+ + + All styles + + +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/code.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/code.html new file mode 100644 index 0000000000..31aa7a6dba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/code.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Inline code +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/combined.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/combined.html new file mode 100644 index 0000000000..3bcf4491b2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/combined.html @@ -0,0 +1,13 @@ +
+
+
+
+

+ + Bold and italic + +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/italic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/italic.html new file mode 100644 index 0000000000..265708f07d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/italic.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Italic text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/mixedInParagraph.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/mixedInParagraph.html new file mode 100644 index 0000000000..b8f217f641 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/mixedInParagraph.html @@ -0,0 +1,15 @@ +
+
+
+
+

+ Normal + bold + italic + code + strike +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/strike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/strike.html new file mode 100644 index 0000000000..294425c21f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/strike.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Strikethrough text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/textColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/textColor.html new file mode 100644 index 0000000000..0e6799d766 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/textColor.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Colored text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/underline.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/underline.html new file mode 100644 index 0000000000..29ab7e88cf --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/underline.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Underline text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/advancedExample.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/advancedExample.html new file mode 100644 index 0000000000..1327b27445 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/advancedExample.html @@ -0,0 +1,82 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+

This row has headers

+
+

+ This is + RED +

+
+

Text is Blue

+
+

+ This spans 2 columns +
+ and 2 rows +

+
+

Sooo many features

+
+

+
+

A cell

+
+

Another Cell

+
+

Aligned center

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/cellTextAlignment.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/cellTextAlignment.html new file mode 100644 index 0000000000..8b1b1756b0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/cellTextAlignment.html @@ -0,0 +1,33 @@ +
+
+
+
+
+
+ + + + + + + + + + + +
+

Left

+
+

Center

+
+

Right

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/emptyCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/emptyCells.html new file mode 100644 index 0000000000..b3564081f0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/emptyCells.html @@ -0,0 +1,37 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + +
+

Has content

+
+

+
+

+
+

Also has content

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/hardBreakInCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/hardBreakInCell.html new file mode 100644 index 0000000000..d0c5618ffc --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/hardBreakInCell.html @@ -0,0 +1,33 @@ +
+
+
+
+
+
+ + + + + + + + + +
+

+ Line 1 +
+ Line 2 +

+
+

Normal cell

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/headerRowsAndCols.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/headerRowsAndCols.html new file mode 100644 index 0000000000..945741a785 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/headerRowsAndCols.html @@ -0,0 +1,44 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+

Corner

+
+

Column Header 1

+
+

Column Header 2

+
+

Row Header 1

+
+

Data 1

+
+

Data 2

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/linksInCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/linksInCells.html new file mode 100644 index 0000000000..8aa07b4b98 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/linksInCells.html @@ -0,0 +1,56 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + +
+

+ Visit + Example +

+
+

Plain cell

+
+

Data

+
+

+ Link +

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/singleCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/singleCell.html new file mode 100644 index 0000000000..63c4da11a1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/singleCell.html @@ -0,0 +1,25 @@ +
+
+
+
+
+
+ + + + + + + +
+

Only cell

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/styledCellContent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/styledCellContent.html new file mode 100644 index 0000000000..9d8035d5c5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/styledCellContent.html @@ -0,0 +1,45 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + +
+

+ Bold +

+
+

+ Italic +

+
+

+ Strike +

+
+

+ Code +

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html index 916688f455..78babe7a23 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html @@ -17,8 +17,9 @@ src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm" controls="" draggable="false" - width="0" > + + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video/withCaption.html new file mode 100644 index 0000000000..951b9d8b86 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video/withCaption.html @@ -0,0 +1,31 @@ +
+
+
+
+
+
+ + + +
+
Video caption
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/basic.html new file mode 100644 index 0000000000..d2a69001ba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/basic.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/button.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/button.html new file mode 100644 index 0000000000..915743c700 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/button.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/noName.html new file mode 100644 index 0000000000..1699b58c73 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/noName.html @@ -0,0 +1,4 @@ + \ No newline at end of file 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 df88fa0937..a7db81b06b 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,8 +1,5 @@
-  
-    const hello ='world';
-    
- console.log(hello); -
-
+ const hello = 'world'; +console.log(hello); +
\ 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 new file mode 100644 index 0000000000..421d420c08 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html @@ -0,0 +1,30 @@ +

Document Title

+

Introduction paragraph.

+

Section 1

+

+ Text with + bold + and + a link + . +

+
    +
  • +

    First point

    +
  • +
  • +

    Second point

    +
  • +
+
+
A notable quote
+
+  const x = 42;
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html index cc675c57a7..90ce06d701 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html @@ -1 +1 @@ -

Add file

\ No newline at end of file + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html index 701b5d4213..54664cf2b1 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html @@ -1,13 +1,19 @@

Link1
Link2

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html index 2c762aedc5..930c29aaab 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html @@ -1,13 +1,19 @@

Link1
Link1

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h1.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h1.html new file mode 100644 index 0000000000..ac06cdc123 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h1.html @@ -0,0 +1 @@ +

Heading 1

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h2.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h2.html new file mode 100644 index 0000000000..92e9734754 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h2.html @@ -0,0 +1 @@ +

Heading 2

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h3.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h3.html new file mode 100644 index 0000000000..df25998db1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h3.html @@ -0,0 +1 @@ +

Heading 3

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h4.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h4.html new file mode 100644 index 0000000000..430144bc54 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h4.html @@ -0,0 +1 @@ +

Heading 4

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h5.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h5.html new file mode 100644 index 0000000000..02e7e8fda2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h5.html @@ -0,0 +1 @@ +
Heading 5
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h6.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h6.html new file mode 100644 index 0000000000..6e76905810 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h6.html @@ -0,0 +1 @@ +
Heading 6
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/styled.html new file mode 100644 index 0000000000..7f14fdb711 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/styled.html @@ -0,0 +1,4 @@ +

+ Bold + Heading +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/toggleable.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/toggleable.html new file mode 100644 index 0000000000..ecec05b566 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/toggleable.html @@ -0,0 +1,6 @@ +
+ +

Toggle Heading

+
+

Child content

+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html index ef7342fe92..a9efaefefc 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html @@ -1,5 +1,5 @@ BlockNote image \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html index 8553433aff..df18852143 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html @@ -1 +1 @@ -

Add image

\ No newline at end of file + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html index 04ccf17c56..667cef41ab 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html @@ -1,5 +1,5 @@
- Caption +
Caption
- Caption +
Caption
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html index 686fc7d4e5..47f0cbe255 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html @@ -1,4 +1,4 @@
- Caption +
Caption
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/urlOnly.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/urlOnly.html new file mode 100644 index 0000000000..41960a99f8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/urlOnly.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/withCaption.html new file mode 100644 index 0000000000..3ecba73103 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/withCaption.html @@ -0,0 +1,8 @@ +
+ Example Image +
This is a caption
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html index db99691d33..057be7e7f6 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html @@ -1,12 +1,18 @@

Website Website2

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html index 4b61e8c582..610e86b2dd 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html @@ -1,7 +1,10 @@

Website

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/plainUrl.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/plainUrl.html new file mode 100644 index 0000000000..d014edd8bb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/plainUrl.html @@ -0,0 +1,10 @@ +

+ https://www.website.com +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html index fb7737f7f8..189a36f5e8 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html @@ -1,14 +1,20 @@

Web site

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/urlWithParens.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/urlWithParens.html new file mode 100644 index 0000000000..a783db1f73 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/urlWithParens.html @@ -0,0 +1,10 @@ +

+ Example +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/withCode.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/withCode.html new file mode 100644 index 0000000000..385c0be8c9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/withCode.html @@ -0,0 +1,13 @@ +

+ See the + docs + for + config +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html index d0b661f0ce..1d9f613297 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html @@ -24,6 +24,10 @@

Check List Item 2

  • -

    Toggle List Item 1

    +
    + +

    Toggle List Item 1

    +
    +
  • \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html index fc16110695..113e6a5d08 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html @@ -20,7 +20,11 @@

    Check List Item 2

    • -

      Toggle List Item 1

      +
      + +

      Toggle List Item 1

      +
      +
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/numberedListStart.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/numberedListStart.html new file mode 100644 index 0000000000..35535f7db4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/numberedListStart.html @@ -0,0 +1,8 @@ +
      +
    1. +

      Item 5

      +
    2. +
    3. +

      Item 6

      +
    4. +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/toggleWithChildren.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/toggleWithChildren.html new file mode 100644 index 0000000000..3712487eb1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/toggleWithChildren.html @@ -0,0 +1,17 @@ +
      +
    • +
      + +

      Toggle List Item

      +
      +

      Toggle Child 1

      +

      Toggle Child 2

      +
      +
    • +
    +
    + +

    Toggle Heading

    +
    +

    Heading Child 1

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/multiple.html new file mode 100644 index 0000000000..a183a01cd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/multiple.html @@ -0,0 +1,3 @@ +

    First paragraph

    +

    Second paragraph

    +

    Third paragraph

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/basic.html new file mode 100644 index 0000000000..53c51228f1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/basic.html @@ -0,0 +1 @@ +
    This is a quote
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/multiple.html new file mode 100644 index 0000000000..80b8a40ae5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/multiple.html @@ -0,0 +1,2 @@ +
    First quote
    +
    Second quote
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/nested.html new file mode 100644 index 0000000000..3e74d08d92 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/nested.html @@ -0,0 +1,2 @@ +
    Parent quote
    +

    Nested paragraph

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/styled.html new file mode 100644 index 0000000000..7f80b7fc7c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/styled.html @@ -0,0 +1,6 @@ +
    + Bold + and + italic + quote +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/withLink.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/withLink.html new file mode 100644 index 0000000000..c893fa67ee --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/withLink.html @@ -0,0 +1,11 @@ +
    + Quote with + a link +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/backgroundColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/backgroundColor.html new file mode 100644 index 0000000000..66f327e85d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/backgroundColor.html @@ -0,0 +1,8 @@ +

    + Highlighted text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/bold.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/bold.html new file mode 100644 index 0000000000..e57a879f6e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/bold.html @@ -0,0 +1,3 @@ +

    + Bold text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/boldItalicStrike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/boldItalicStrike.html new file mode 100644 index 0000000000..d7506fe610 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/boldItalicStrike.html @@ -0,0 +1,7 @@ +

    + + + All styles + + +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/code.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/code.html new file mode 100644 index 0000000000..6fe865b744 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/code.html @@ -0,0 +1,3 @@ +

    + Inline code +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/combined.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/combined.html new file mode 100644 index 0000000000..920576e90a --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/combined.html @@ -0,0 +1,5 @@ +

    + + Bold and italic + +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/italic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/italic.html new file mode 100644 index 0000000000..fcff5726e6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/italic.html @@ -0,0 +1,3 @@ +

    + Italic text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/mixedInParagraph.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/mixedInParagraph.html new file mode 100644 index 0000000000..369fd1b3bf --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/mixedInParagraph.html @@ -0,0 +1,7 @@ +

    + Normal + bold + italic + code + strike +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/strike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/strike.html new file mode 100644 index 0000000000..abfabbe4e6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/strike.html @@ -0,0 +1,3 @@ +

    + Strikethrough text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/textColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/textColor.html new file mode 100644 index 0000000000..798166dcad --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/textColor.html @@ -0,0 +1,8 @@ +

    + Colored text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/underline.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/underline.html new file mode 100644 index 0000000000..f861031c9e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/underline.html @@ -0,0 +1,3 @@ +

    + Underline text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/advancedExample.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/advancedExample.html new file mode 100644 index 0000000000..a4f5dfcb31 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/advancedExample.html @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + +
    +

    This row has headers

    +
    +

    + This is + RED +

    +
    +

    Text is Blue

    +
    +

    + This spans 2 columns +
    + and 2 rows +

    +
    +

    Sooo many features

    +
    +

    +
    +

    A cell

    +
    +

    Another Cell

    +
    +

    Aligned center

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/cellTextAlignment.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/cellTextAlignment.html new file mode 100644 index 0000000000..21f5f0ab0c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/cellTextAlignment.html @@ -0,0 +1,18 @@ + + + + + + + + + + + +
    +

    Left

    +
    +

    Center

    +
    +

    Right

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/emptyCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/emptyCells.html new file mode 100644 index 0000000000..10a9fb5259 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/emptyCells.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + +
    +

    Has content

    +
    +

    +
    +

    +
    +

    Also has content

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/hardBreakInCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/hardBreakInCell.html new file mode 100644 index 0000000000..a313a5323d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/hardBreakInCell.html @@ -0,0 +1,18 @@ + + + + + + + + + +
    +

    + Line 1 +
    + Line 2 +

    +
    +

    Normal cell

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/headerRowsAndCols.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/headerRowsAndCols.html new file mode 100644 index 0000000000..64ee4183aa --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/headerRowsAndCols.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + +
    +

    Corner

    +
    +

    Column Header 1

    +
    +

    Column Header 2

    +
    +

    Row Header 1

    +
    +

    Data 1

    +
    +

    Data 2

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/linksInCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/linksInCells.html new file mode 100644 index 0000000000..0cafd0eda1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/linksInCells.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + +
    +

    + Visit + Example +

    +
    +

    Plain cell

    +
    +

    Data

    +
    +

    + Link +

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/singleCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/singleCell.html new file mode 100644 index 0000000000..ce8bea5831 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/singleCell.html @@ -0,0 +1,10 @@ + + + + + + + +
    +

    Only cell

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/styledCellContent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/styledCellContent.html new file mode 100644 index 0000000000..91db4da0e4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/styledCellContent.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + +
    +

    + Bold +

    +
    +

    + Italic +

    +
    +

    + Strike +

    +
    +

    + Code +

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/video/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/video/withCaption.html new file mode 100644 index 0000000000..978dcc0448 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/video/withCaption.html @@ -0,0 +1,8 @@ +
    + +
    Video caption
    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/basic.md new file mode 100644 index 0000000000..cdcf0dde54 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/basic.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/button.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/button.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/button.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/noName.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/noName.md new file mode 100644 index 0000000000..cdcf0dde54 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/noName.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/document.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/document.md new file mode 100644 index 0000000000..47cf4739db --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/document.md @@ -0,0 +1,18 @@ +# Document Title + +Introduction paragraph. + +## Section 1 + +Text with **bold** and [a link](https://example.com). + +* First point +* Second point + +*** + +> A notable quote + +```javascript +const x = 42; +``` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md index 4a2de0a7fc..fca446bec3 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md @@ -1,4 +1,4 @@ -## **Heading ***~~2~~* +## **Heading** *~~2~~* Paragraph diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md index 8d3fa6a207..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md @@ -1 +1 @@ -Add file + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md index c7fefc547f..4cca42f87d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md @@ -1,3 +1,3 @@ -[exampleURL](exampleURL) +exampleURL Caption diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md index e69de29bb2..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h1.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h1.md new file mode 100644 index 0000000000..bd706e91c4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h1.md @@ -0,0 +1 @@ +# Heading 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h2.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h2.md new file mode 100644 index 0000000000..cd760a44ba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h2.md @@ -0,0 +1 @@ +## Heading 2 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h3.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h3.md new file mode 100644 index 0000000000..607fcc43b6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h3.md @@ -0,0 +1 @@ +### Heading 3 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h4.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h4.md new file mode 100644 index 0000000000..9c7bd7c52e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h4.md @@ -0,0 +1 @@ +#### Heading 4 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h5.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h5.md new file mode 100644 index 0000000000..2410fdf2b0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h5.md @@ -0,0 +1 @@ +##### Heading 5 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h6.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h6.md new file mode 100644 index 0000000000..848d83e6dd --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h6.md @@ -0,0 +1 @@ +###### Heading 6 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/styled.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/styled.md new file mode 100644 index 0000000000..90c78848fd --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/styled.md @@ -0,0 +1 @@ +# **Bold** Heading diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/toggleable.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/toggleable.md new file mode 100644 index 0000000000..cc8cbf3aa9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/toggleable.md @@ -0,0 +1,3 @@ +## Toggle Heading + +Child content diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md index 3219bb9f00..58d07ff1a4 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md @@ -1 +1 @@ -![BlockNote image](https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png) +![](https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md index b350ae21e0..5a88869a1b 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md @@ -1,3 +1 @@ -![example](exampleURL) - -Caption +
    example
    Caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md index 02184caf8a..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md @@ -1 +1 @@ -Add image + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md index 7a13551364..99ff1825d4 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md @@ -1,7 +1,3 @@ -![Caption](exampleURL) +
    Caption
    -Caption - -![Caption](exampleURL) - -Caption +
    Caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md index c6b5864d90..ba1c350f13 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md @@ -1,3 +1 @@ -![Caption](exampleURL) - -Caption +
    Caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/urlOnly.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/urlOnly.md new file mode 100644 index 0000000000..f667f8e031 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/urlOnly.md @@ -0,0 +1 @@ +![](exampleURL) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/withCaption.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/withCaption.md new file mode 100644 index 0000000000..fb8426fd87 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/withCaption.md @@ -0,0 +1 @@ +
    Example Image
    This is a caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/plainUrl.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/plainUrl.md new file mode 100644 index 0000000000..0ba1e54028 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/plainUrl.md @@ -0,0 +1 @@ +https://www.website.com diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/urlWithParens.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/urlWithParens.md new file mode 100644 index 0000000000..ebe94f4ffe --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/urlWithParens.md @@ -0,0 +1 @@ +[Example](https://en.wikipedia.org/wiki/Example_\(disambiguation\)) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/withCode.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/withCode.md new file mode 100644 index 0000000000..090ae185e1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/withCode.md @@ -0,0 +1 @@ +See the [docs](https://example.com) for `config` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md index f092d8bf95..a083f00804 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md @@ -1,13 +1,9 @@ * Bullet List Item 1 - * Bullet List Item 2 1. Numbered List Item 1 - 2. Numbered List Item 2 * [ ] Check List Item 1 - * [x] Check List Item 2 - * Toggle List Item 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md index c43ddb13ef..a0388ef96d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md @@ -1,13 +1,7 @@ * Bullet List Item 1 - * Bullet List Item 2 - 1. Numbered List Item 1 - 2. Numbered List Item 2 - * [ ] Check List Item 1 - * [x] Check List Item 2 - * Toggle List Item 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/numberedListStart.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/numberedListStart.md new file mode 100644 index 0000000000..bb4415d690 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/numberedListStart.md @@ -0,0 +1,2 @@ +5. Item 5 +6. Item 6 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/toggleWithChildren.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/toggleWithChildren.md new file mode 100644 index 0000000000..a01cab2790 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/toggleWithChildren.md @@ -0,0 +1,9 @@ +* Toggle List Item + + Toggle Child 1 + + Toggle Child 2 + +## Toggle Heading + +Heading Child 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md index e69de29bb2..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md index e69de29bb2..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/multiple.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/multiple.md new file mode 100644 index 0000000000..8fadfa1d86 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/multiple.md @@ -0,0 +1,5 @@ +First paragraph + +Second paragraph + +Third paragraph diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/basic.md new file mode 100644 index 0000000000..83d6a8096d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/basic.md @@ -0,0 +1 @@ +> This is a quote diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/multiple.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/multiple.md new file mode 100644 index 0000000000..c2610d0ba7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/multiple.md @@ -0,0 +1,3 @@ +> First quote + +> Second quote diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/nested.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/nested.md new file mode 100644 index 0000000000..41c50517f2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/nested.md @@ -0,0 +1,3 @@ +> Parent quote + +Nested paragraph diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/styled.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/styled.md new file mode 100644 index 0000000000..71e0af0173 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/styled.md @@ -0,0 +1 @@ +> **Bold** and *italic* quote diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/withLink.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/withLink.md new file mode 100644 index 0000000000..8510d4defd --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/withLink.md @@ -0,0 +1 @@ +> Quote with [a link](https://www.example.com) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/backgroundColor.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/backgroundColor.md new file mode 100644 index 0000000000..3ba8964656 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/backgroundColor.md @@ -0,0 +1 @@ +Highlighted text diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/bold.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/bold.md new file mode 100644 index 0000000000..df2474d633 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/bold.md @@ -0,0 +1 @@ +**Bold text** diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/boldItalicStrike.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/boldItalicStrike.md new file mode 100644 index 0000000000..1af450cf5e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/boldItalicStrike.md @@ -0,0 +1 @@ +***~~All styles~~*** diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/code.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/code.md new file mode 100644 index 0000000000..aa4775ec76 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/code.md @@ -0,0 +1 @@ +`Inline code` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/combined.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/combined.md new file mode 100644 index 0000000000..b011bd3c15 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/combined.md @@ -0,0 +1 @@ +***Bold and italic*** diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/italic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/italic.md new file mode 100644 index 0000000000..c6c83dc114 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/italic.md @@ -0,0 +1 @@ +*Italic text* diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/mixedInParagraph.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/mixedInParagraph.md new file mode 100644 index 0000000000..76bd55f326 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/mixedInParagraph.md @@ -0,0 +1 @@ +Normal **bold** *italic* `code `~~strike~~ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/strike.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/strike.md new file mode 100644 index 0000000000..afe555a038 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/strike.md @@ -0,0 +1 @@ +~~Strikethrough text~~ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/textColor.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/textColor.md new file mode 100644 index 0000000000..28f332d788 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/textColor.md @@ -0,0 +1 @@ +Colored text diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/underline.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/underline.md new file mode 100644 index 0000000000..2ccc77398c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/underline.md @@ -0,0 +1 @@ +Underline text diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/advancedExample.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/advancedExample.md new file mode 100644 index 0000000000..53599a4feb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/advancedExample.md @@ -0,0 +1,6 @@ +| This row has headers | This is **RED** | Text is Blue | +| -------------------------------- | --------------- | ------------------ | +| This spans 2 columns\ +and 2 rows | | Sooo many features | +| | | | +| A cell | Another Cell | Aligned center | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/cellTextAlignment.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/cellTextAlignment.md new file mode 100644 index 0000000000..d3d4211640 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/cellTextAlignment.md @@ -0,0 +1,3 @@ +| | | | +| ---------- | ---------- | ---------- | +| Left | Center | Right | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/emptyCells.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/emptyCells.md new file mode 100644 index 0000000000..ba81ea2ca9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/emptyCells.md @@ -0,0 +1,4 @@ +| | | +| ----------- | ---------------- | +| Has content | | +| | Also has content | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/hardBreakInCell.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/hardBreakInCell.md new file mode 100644 index 0000000000..d9ffaf65a3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/hardBreakInCell.md @@ -0,0 +1,4 @@ +| | | +| -------------- | ----------- | +| Line 1\ +Line 2 | Normal cell | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/headerRowsAndCols.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/headerRowsAndCols.md new file mode 100644 index 0000000000..1c29ad9f94 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/headerRowsAndCols.md @@ -0,0 +1,3 @@ +| Corner | Column Header 1 | Column Header 2 | +| ------------ | --------------- | --------------- | +| Row Header 1 | Data 1 | Data 2 | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/linksInCells.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/linksInCells.md new file mode 100644 index 0000000000..7815cfe199 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/linksInCells.md @@ -0,0 +1,4 @@ +| | | +| ------------------------------------ | ---------------------------- | +| Visit [Example](https://example.com) | Plain cell | +| Data | [Link](https://example2.com) | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/singleCell.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/singleCell.md new file mode 100644 index 0000000000..3be705c3e8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/singleCell.md @@ -0,0 +1,3 @@ +| | +| ---------- | +| Only cell | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/styledCellContent.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/styledCellContent.md new file mode 100644 index 0000000000..ff5eff81a2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/styledCellContent.md @@ -0,0 +1,4 @@ +| | | +| ---------- | ---------- | +| **Bold** | *Italic* | +| ~~Strike~~ | `Code` | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/video/withCaption.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/video/withCaption.md new file mode 100644 index 0000000000..78cb313b4b --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/video/withCaption.md @@ -0,0 +1 @@ +
    Video caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/basic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/basic.json new file mode 100644 index 0000000000..32e28f6803 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/basic.json @@ -0,0 +1,20 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "example", + "showPreview": true, + "url": "https://example.com/audio.mp3", + }, + "type": "audio", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/button.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/button.json new file mode 100644 index 0000000000..2149eef7c8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/button.json @@ -0,0 +1,20 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "url": "", + }, + "type": "audio", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/noName.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/noName.json new file mode 100644 index 0000000000..a9b3396ea4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/noName.json @@ -0,0 +1,20 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "url": "https://example.com/audio.mp3", + }, + "type": "audio", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/complex/document.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/complex/document.json new file mode 100644 index 0000000000..0eb89bbcda --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/complex/document.json @@ -0,0 +1,219 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Document Title", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Introduction paragraph.", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "3", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Section 1", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "4", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Text with ", + "type": "text", + }, + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "bold", + "type": "text", + }, + { + "text": " and ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://example.com", + }, + "type": "link", + }, + ], + "text": "a link", + "type": "text", + }, + { + "text": ".", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "5", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "First point", + "type": "text", + }, + ], + "type": "bulletListItem", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "6", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Second point", + "type": "text", + }, + ], + "type": "bulletListItem", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "7", + }, + "content": [ + { + "type": "divider", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "8", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "A notable quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "9", + }, + "content": [ + { + "attrs": { + "language": "javascript", + }, + "content": [ + { + "text": "const x = 42;", + "type": "text", + }, + ], + "type": "codeBlock", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json index 174eeecd5b..9db2d101f6 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -33,10 +30,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website2.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json index 4ae3cc342b..7a3369fd10 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -33,10 +30,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h1.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h1.json new file mode 100644 index 0000000000..d147b23ade --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h1.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 1", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h2.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h2.json new file mode 100644 index 0000000000..f9f92e7081 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h2.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 2", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h3.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h3.json new file mode 100644 index 0000000000..6399a58563 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h3.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 3", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h4.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h4.json new file mode 100644 index 0000000000..c23a0c4809 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h4.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 4, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 4", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h5.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h5.json new file mode 100644 index 0000000000..0867b7796f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h5.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 5, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 5", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h6.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h6.json new file mode 100644 index 0000000000..b5eddefddc --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h6.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 6, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 6", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/styled.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/styled.json new file mode 100644 index 0000000000..ff107bc15f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/styled.json @@ -0,0 +1,35 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold ", + "type": "text", + }, + { + "text": "Heading", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/toggleable.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/toggleable.json new file mode 100644 index 0000000000..7bb4c57f86 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/toggleable.json @@ -0,0 +1,53 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": true, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Heading", + "type": "text", + }, + ], + "type": "heading", + }, + { + "content": [ + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Child content", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/urlOnly.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/urlOnly.json new file mode 100644 index 0000000000..3d56a77d23 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/urlOnly.json @@ -0,0 +1,22 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": undefined, + "showPreview": true, + "textAlignment": "left", + "url": "exampleURL", + }, + "type": "image", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/withCaption.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/withCaption.json new file mode 100644 index 0000000000..5016f16a10 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/withCaption.json @@ -0,0 +1,22 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "This is a caption", + "name": "Example Image", + "previewWidth": undefined, + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json index d546271743..f3020cdf7f 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -30,10 +27,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website2.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json index 3964520c13..b85306efc8 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/plainUrl.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/plainUrl.json new file mode 100644 index 0000000000..dc5aa26397 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/plainUrl.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "href": "https://www.website.com", + }, + "type": "link", + }, + ], + "text": "https://www.website.com", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json index 84c3a57c95..2c01e6c1a1 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json @@ -18,10 +18,7 @@ }, { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -33,10 +30,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/urlWithParens.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/urlWithParens.json new file mode 100644 index 0000000000..4de7926c89 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/urlWithParens.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "href": "https://en.wikipedia.org/wiki/Example_(disambiguation)", + }, + "type": "link", + }, + ], + "text": "Example", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/withCode.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/withCode.json new file mode 100644 index 0000000000..bf1f1da31f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/withCode.json @@ -0,0 +1,49 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "See the ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://example.com", + }, + "type": "link", + }, + ], + "text": "docs", + "type": "text", + }, + { + "text": " for ", + "type": "text", + }, + { + "marks": [ + { + "type": "code", + }, + ], + "text": "config", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/numberedListStart.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/numberedListStart.json new file mode 100644 index 0000000000..387b4fa073 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/numberedListStart.json @@ -0,0 +1,48 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "start": 5, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Item 5", + "type": "text", + }, + ], + "type": "numberedListItem", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "start": undefined, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Item 6", + "type": "text", + }, + ], + "type": "numberedListItem", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/toggleWithChildren.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/toggleWithChildren.json new file mode 100644 index 0000000000..9b7407a060 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/toggleWithChildren.json @@ -0,0 +1,124 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle List Item", + "type": "text", + }, + ], + "type": "toggleListItem", + }, + { + "content": [ + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Child 1", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "3", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Child 2", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "4", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": true, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Heading", + "type": "text", + }, + ], + "type": "heading", + }, + { + "content": [ + { + "attrs": { + "id": "5", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading Child 1", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/multiple.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/multiple.json new file mode 100644 index 0000000000..affba0772c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/multiple.json @@ -0,0 +1,68 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "First paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Second paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "3", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Third paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/basic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/basic.json new file mode 100644 index 0000000000..9234a8f05f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/basic.json @@ -0,0 +1,23 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "This is a quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/multiple.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/multiple.json new file mode 100644 index 0000000000..458e24879a --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/multiple.json @@ -0,0 +1,44 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "First quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "Second quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/nested.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/nested.json new file mode 100644 index 0000000000..b3c46ed220 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/nested.json @@ -0,0 +1,50 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "Parent quote", + "type": "text", + }, + ], + "type": "quote", + }, + { + "content": [ + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Nested paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/styled.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/styled.json new file mode 100644 index 0000000000..a482c2ad58 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/styled.json @@ -0,0 +1,45 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold ", + "type": "text", + }, + { + "text": "and ", + "type": "text", + }, + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "italic", + "type": "text", + }, + { + "text": " quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/withLink.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/withLink.json new file mode 100644 index 0000000000..c0e2857679 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/withLink.json @@ -0,0 +1,35 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "Quote with ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://www.example.com", + }, + "type": "link", + }, + ], + "text": "a link", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/backgroundColor.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/backgroundColor.json new file mode 100644 index 0000000000..e07954b219 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/backgroundColor.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "stringValue": "blue", + }, + "type": "backgroundColor", + }, + ], + "text": "Highlighted text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/bold.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/bold.json new file mode 100644 index 0000000000..b6d5b7a208 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/bold.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/boldItalicStrike.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/boldItalicStrike.json new file mode 100644 index 0000000000..7c5f05d763 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/boldItalicStrike.json @@ -0,0 +1,35 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + { + "type": "italic", + }, + { + "type": "strike", + }, + ], + "text": "All styles", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/code.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/code.json new file mode 100644 index 0000000000..2cf0463e03 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/code.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "code", + }, + ], + "text": "Inline code", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/combined.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/combined.json new file mode 100644 index 0000000000..5b91fb8abb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/combined.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + { + "type": "italic", + }, + ], + "text": "Bold and italic", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/italic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/italic.json new file mode 100644 index 0000000000..7d22809e81 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/italic.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "Italic text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/mixedInParagraph.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/mixedInParagraph.json new file mode 100644 index 0000000000..d72aa1d3bb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/mixedInParagraph.json @@ -0,0 +1,60 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Normal ", + "type": "text", + }, + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "bold ", + "type": "text", + }, + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "italic ", + "type": "text", + }, + { + "marks": [ + { + "type": "code", + }, + ], + "text": "code ", + "type": "text", + }, + { + "marks": [ + { + "type": "strike", + }, + ], + "text": "strike", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/strike.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/strike.json new file mode 100644 index 0000000000..756569ade9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/strike.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "strike", + }, + ], + "text": "Strikethrough text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/textColor.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/textColor.json new file mode 100644 index 0000000000..7bca812fd7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/textColor.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "stringValue": "red", + }, + "type": "textColor", + }, + ], + "text": "Colored text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/underline.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/underline.json new file mode 100644 index 0000000000..348c2ae742 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/underline.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "underline", + }, + ], + "text": "Underline text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/advancedExample.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/advancedExample.json new file mode 100644 index 0000000000..1fdde23a6b --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/advancedExample.json @@ -0,0 +1,265 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 199, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "This row has headers", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "red", + "colspan": 1, + "colwidth": [ + 148, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "This is ", + "type": "text", + }, + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "RED", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "blue", + }, + "content": [ + { + "content": [ + { + "text": "Text is Blue", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "yellow", + "colspan": 2, + "colwidth": [ + 199, + 148, + ], + "rowspan": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "This spans 2 columns", + "type": "text", + }, + { + "type": "hardBreak", + }, + { + "text": "and 2 rows", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "gray", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Sooo many features", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "gray", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "purple", + }, + "content": [ + { + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 199, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "A cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 148, + ], + "rowspan": 1, + "textAlignment": "right", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Another Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Aligned center", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/cellTextAlignment.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/cellTextAlignment.json new file mode 100644 index 0000000000..c7ded0cfc6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/cellTextAlignment.json @@ -0,0 +1,89 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Left", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Center", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "right", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Right", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/emptyCells.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/emptyCells.json new file mode 100644 index 0000000000..05bb8aa0af --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/emptyCells.json @@ -0,0 +1,104 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Has content", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Also has content", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/hardBreakInCell.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/hardBreakInCell.json new file mode 100644 index 0000000000..121834448d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/hardBreakInCell.json @@ -0,0 +1,74 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Line 1", + "type": "text", + }, + { + "type": "hardBreak", + }, + { + "text": "Line 2", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Normal cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/headerRowsAndCols.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/headerRowsAndCols.json new file mode 100644 index 0000000000..606ec05c49 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/headerRowsAndCols.json @@ -0,0 +1,160 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Corner", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Column Header 1", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Column Header 2", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Row Header 1", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Data 1", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Data 2", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/linksInCells.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/linksInCells.json new file mode 100644 index 0000000000..6f491b4f48 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/linksInCells.json @@ -0,0 +1,136 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Visit ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://example.com", + }, + "type": "link", + }, + ], + "text": "Example", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Plain cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Data", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "attrs": { + "href": "https://example2.com", + }, + "type": "link", + }, + ], + "text": "Link", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/singleCell.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/singleCell.json new file mode 100644 index 0000000000..dd4628d177 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/singleCell.json @@ -0,0 +1,45 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Only cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/styledCellContent.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/styledCellContent.json new file mode 100644 index 0000000000..3727fa1cf0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/styledCellContent.json @@ -0,0 +1,136 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "Italic", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "strike", + }, + ], + "text": "Strike", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "code", + }, + ], + "text": "Code", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/video/withCaption.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/video/withCaption.json new file mode 100644 index 0000000000..6d6c134eb0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/video/withCaption.json @@ -0,0 +1,22 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "Video caption", + "name": "Example Video", + "previewWidth": undefined, + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/video.mp4", + }, + "type": "video", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts index 899331b55d..bd8cb77493 100644 --- a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts +++ b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts @@ -159,6 +159,42 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + testCase: { + name: "lists/toggleWithChildren", + content: [ + { + type: "toggleListItem", + content: "Toggle List Item", + children: [ + { + type: "paragraph", + content: "Toggle Child 1", + }, + { + type: "paragraph", + content: "Toggle Child 2", + }, + ], + }, + { + type: "heading", + props: { + level: 2, + isToggleable: true, + }, + content: "Toggle Heading", + children: [ + { + type: "paragraph", + content: "Heading Child 1", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "lists/nested", @@ -200,54 +236,701 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, ], }, - ], - }, - executeTest: testExportBlockNoteHTML, - }, - { - testCase: { - name: "codeBlock/empty", - content: [ + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/empty", + content: [ + { + type: "codeBlock", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/defaultLanguage", + content: [ + { + type: "codeBlock", + content: "console.log('Hello, world!');", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/python", + content: [ + { + type: "codeBlock", + props: { language: "python" }, + content: "print('Hello, world!')", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/contains-newlines", + content: [ + { + type: "codeBlock", + props: { language: "javascript" }, + content: "const hello = 'world';\nconsole.log(hello);\n", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Heading levels + { + testCase: { + name: "heading/h1", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Heading 1", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h2", + content: [ + { + type: "heading", + props: { level: 2 }, + content: "Heading 2", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h3", + content: [ + { + type: "heading", + props: { level: 3 }, + content: "Heading 3", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h4", + content: [ + { + type: "heading", + props: { level: 4 }, + content: "Heading 4", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h5", + content: [ + { + type: "heading", + props: { level: 5 }, + content: "Heading 5", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h6", + content: [ + { + type: "heading", + props: { level: 6 }, + content: "Heading 6", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/styled", + content: [ + { + type: "heading", + props: { level: 1 }, + content: [ + { + type: "text", + text: "Bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "Heading", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/toggleable", + content: [ + { + type: "heading", + props: { level: 2, isToggleable: true }, + content: "Toggle Heading", + children: [ + { + type: "paragraph", + content: "Child content", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Quote / Blockquote + { + testCase: { + name: "quote/basic", + content: [ + { + type: "quote", + content: "This is a quote", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/styled", + content: [ + { + type: "quote", + content: [ + { + type: "text", + text: "Bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "and ", + styles: {}, + }, + { + type: "text", + text: "italic", + styles: { italic: true }, + }, + { + type: "text", + text: " quote", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/withLink", + content: [ + { + type: "quote", + content: [ + { + type: "text", + text: "Quote with ", + styles: {}, + }, + { + type: "link", + href: "https://www.example.com", + content: "a link", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/nested", + content: [ + { + type: "quote", + content: "Parent quote", + children: [ + { + type: "paragraph", + content: "Nested paragraph", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/multiple", + content: [ + { + type: "quote", + content: "First quote", + }, + { + type: "quote", + content: "Second quote", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Audio + { + testCase: { + name: "audio/basic", + content: [ + { + type: "audio", + props: { + url: "https://example.com/audio.mp3", + name: "example", + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "audio/button", + content: [ + { + type: "audio", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "audio/noName", + content: [ + { + type: "audio", + props: { + url: "https://example.com/audio.mp3", + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Individual styles + { + testCase: { + name: "style/bold", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold text", + styles: { bold: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/italic", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Italic text", + styles: { italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/underline", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Underline text", + styles: { underline: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/strike", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Strikethrough text", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/code", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline code", + styles: { code: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/textColor", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Colored text", + styles: { textColor: "red" }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/backgroundColor", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Highlighted text", + styles: { backgroundColor: "blue" }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/combined", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold and italic", + styles: { bold: true, italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/boldItalicStrike", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "All styles", + styles: { bold: true, italic: true, strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/mixedInParagraph", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Normal ", + styles: {}, + }, + { + type: "text", + text: "bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "italic ", + styles: { italic: true }, + }, + { + type: "text", + text: "code ", + styles: { code: true }, + }, + { + type: "text", + text: "strike", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Numbered list with custom start + { + testCase: { + name: "lists/numberedListStart", + content: [ + { + type: "numberedListItem", + props: { start: 5 }, + content: "Item 5", + }, + { + type: "numberedListItem", + content: "Item 6", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Multiple paragraphs + { + testCase: { + name: "paragraph/multiple", + content: [ + { + type: "paragraph", + content: "First paragraph", + }, + { + type: "paragraph", + content: "Second paragraph", + }, + { + type: "paragraph", + content: "Third paragraph", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Mixed block types document + { + testCase: { + name: "complex/document", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Document Title", + }, + { + type: "paragraph", + content: "Introduction paragraph.", + }, + { + type: "heading", + props: { level: 2 }, + content: "Section 1", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Text with ", + styles: {}, + }, + { + type: "text", + text: "bold", + styles: { bold: true }, + }, + { + type: "text", + text: " and ", + styles: {}, + }, + { + type: "link", + href: "https://example.com", + content: "a link", + }, + { + type: "text", + text: ".", + styles: {}, + }, + ], + }, + { + type: "bulletListItem", + content: "First point", + }, + { + type: "bulletListItem", + content: "Second point", + }, + { + type: "divider", + }, + { + type: "quote", + content: "A notable quote", + }, { type: "codeBlock", + props: { language: "javascript" }, + content: "const x = 42;", }, ], }, executeTest: testExportBlockNoteHTML, }, + // Link with inline code { testCase: { - name: "codeBlock/defaultLanguage", + name: "link/withCode", content: [ { - type: "codeBlock", - content: "console.log('Hello, world!');", + type: "paragraph", + content: [ + { + type: "text", + text: "See the ", + styles: {}, + }, + { + type: "link", + href: "https://example.com", + content: "docs", + }, + { + type: "text", + text: " for ", + styles: {}, + }, + { + type: "text", + text: "config", + styles: { code: true }, + }, + ], }, ], }, executeTest: testExportBlockNoteHTML, }, + // Image with caption { testCase: { - name: "codeBlock/python", + name: "image/withCaption", content: [ { - type: "codeBlock", - props: { language: "python" }, - content: "print('Hello, world!')", + type: "image", + props: { + url: "https://example.com/image.png", + name: "Example Image", + caption: "This is a caption", + }, }, ], }, executeTest: testExportBlockNoteHTML, }, + // Video with caption { testCase: { - name: "codeBlock/contains-newlines", + name: "video/withCaption", content: [ { - type: "codeBlock", - props: { language: "javascript" }, - content: "const hello = 'world';\nconsole.log(hello);\n", + type: "video", + props: { + url: "https://example.com/video.mp4", + name: "Example Video", + caption: "Video caption", + }, }, ], }, @@ -418,6 +1101,23 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + // Image with only a URL — no name, no caption. Confirms markdown export + // stays as plain `![](url)` without wrapping in a `
    ` (the figure + // form is only used to carry caption text through the round-trip). + testCase: { + name: "image/urlOnly", + content: [ + { + type: "image", + props: { + url: "exampleURL", + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "image/noPreview", @@ -455,8 +1155,263 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< caption: "Caption", previewWidth: 256, }, - }, - ], + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "table/basic", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "table/allColWidths", + content: [ + { + type: "table", + content: { + type: "tableContent", + columnWidths: [100, 200, 300], + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, }, ], }, @@ -464,12 +1419,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/basic", + name: "table/mixedColWidths", content: [ { type: "table", content: { type: "tableContent", + columnWidths: [100, undefined, 300], rows: [ { cells: [ @@ -591,14 +1547,51 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/allColWidths", + name: "table/mixedCellColors", content: [ { type: "table", content: { type: "tableContent", - columnWidths: [100, 200, 300], + columnWidths: [100, undefined, 300], rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "red", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "blue", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "blue", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "yellow", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "red", + }, + }, + ], + }, { cells: [ { @@ -673,6 +1666,49 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, ], }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "table/mixedRowspansAndColspans", + content: [ + { + type: "table", + content: { + type: "tableContent", + columnWidths: [100, 200, 300], + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "red", + colspan: 2, + rowspan: 1, + textAlignment: "left", + textColor: "blue", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "yellow", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "red", + }, + }, + ], + }, { cells: [ { @@ -681,11 +1717,26 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< props: { backgroundColor: "default", colspan: 1, + rowspan: 2, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 2, rowspan: 1, textAlignment: "left", textColor: "default", }, }, + ], + }, + { + cells: [ { type: "tableCell", content: ["Table Cell"], @@ -719,13 +1770,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/mixedColWidths", + name: "table/headerRows", content: [ { type: "table", content: { + headerRows: 1, type: "tableContent", - columnWidths: [100, undefined, 300], rows: [ { cells: [ @@ -847,13 +1898,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/mixedCellColors", + name: "table/headerCols", content: [ { type: "table", content: { + headerCols: 1, type: "tableContent", - columnWidths: [100, undefined, 300], rows: [ { cells: [ @@ -861,18 +1912,18 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< type: "tableCell", content: ["Table Cell"], props: { - backgroundColor: "red", + backgroundColor: "default", colspan: 1, rowspan: 1, textAlignment: "left", - textColor: "blue", + textColor: "default", }, }, { type: "tableCell", content: ["Table Cell"], props: { - backgroundColor: "blue", + backgroundColor: "default", colspan: 1, rowspan: 1, textAlignment: "left", @@ -883,20 +1934,113 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< type: "tableCell", content: ["Table Cell"], props: { - backgroundColor: "yellow", + backgroundColor: "default", colspan: 1, rowspan: 1, textAlignment: "left", - textColor: "red", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", }, }, ], }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: header rows + header cols together + { + testCase: { + name: "table/headerRowsAndCols", + content: [ + { + type: "table", + content: { + type: "tableContent", + headerRows: 1, + headerCols: 1, + rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Corner"], props: { backgroundColor: "default", colspan: 1, @@ -907,7 +2051,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Column Header 1"], props: { backgroundColor: "default", colspan: 1, @@ -918,7 +2062,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Column Header 2"], props: { backgroundColor: "default", colspan: 1, @@ -933,7 +2077,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Row Header 1"], props: { backgroundColor: "default", colspan: 1, @@ -944,7 +2088,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Data 1"], props: { backgroundColor: "default", colspan: 1, @@ -955,7 +2099,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Data 2"], props: { backgroundColor: "default", colspan: 1, @@ -973,61 +2117,47 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + // Advanced table: styled content in cells { testCase: { - name: "table/mixedRowspansAndColspans", + name: "table/styledCellContent", content: [ { type: "table", content: { type: "tableContent", - columnWidths: [100, 200, 300], rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], - props: { - backgroundColor: "red", - colspan: 2, - rowspan: 1, - textAlignment: "left", - textColor: "blue", - }, - }, - { - type: "tableCell", - content: ["Table Cell"], - props: { - backgroundColor: "yellow", - colspan: 1, - rowspan: 1, - textAlignment: "left", - textColor: "red", - }, - }, - ], - }, - { - cells: [ - { - type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Bold", + styles: { bold: true }, + }, + ], props: { backgroundColor: "default", colspan: 1, - rowspan: 2, + rowspan: 1, textAlignment: "left", textColor: "default", }, }, { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Italic", + styles: { italic: true }, + }, + ], props: { backgroundColor: "default", - colspan: 2, + colspan: 1, rowspan: 1, textAlignment: "left", textColor: "default", @@ -1039,7 +2169,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Strike", + styles: { strike: true }, + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1050,7 +2186,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Code", + styles: { code: true }, + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1068,21 +2210,32 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + // Advanced table: links in cells { testCase: { - name: "table/headerRows", + name: "table/linksInCells", content: [ { type: "table", content: { - headerRows: 1, type: "tableContent", rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Visit ", + styles: {}, + }, + { + type: "link", + href: "https://example.com", + content: "Example", + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1093,7 +2246,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Plain cell"], props: { backgroundColor: "default", colspan: 1, @@ -1102,9 +2255,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, + ], + }, + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Data"], props: { backgroundColor: "default", colspan: 1, @@ -1113,13 +2270,15 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, - ], - }, - { - cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "link", + href: "https://example2.com", + content: "Link", + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1128,9 +2287,30 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: empty cells + { + testCase: { + name: "table/emptyCells", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Has content"], props: { backgroundColor: "default", colspan: 1, @@ -1141,7 +2321,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: [], props: { backgroundColor: "default", colspan: 1, @@ -1156,7 +2336,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [], props: { backgroundColor: "default", colspan: 1, @@ -1167,7 +2347,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Also has content"], props: { backgroundColor: "default", colspan: 1, @@ -1176,9 +2356,30 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: single cell + { + testCase: { + name: "table/singleCell", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Only cell"], props: { backgroundColor: "default", colspan: 1, @@ -1196,49 +2397,103 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + // Advanced table: from the advanced-tables example (large merged cells) { testCase: { - name: "table/headerCols", + name: "table/advancedExample", content: [ { type: "table", content: { - headerCols: 1, type: "tableContent", + columnWidths: [199, 148, 201], + headerRows: 1, rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["This row has headers"], props: { + colspan: 1, + rowspan: 1, backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "This is ", + styles: {}, + }, + { + type: "text", + text: "RED", + styles: { bold: true }, + }, + ], + props: { colspan: 1, rowspan: 1, - textAlignment: "left", + backgroundColor: "red", textColor: "default", + textAlignment: "center", }, }, { type: "tableCell", - content: ["Table Cell"], + content: ["Text is Blue"], props: { - backgroundColor: "default", colspan: 1, rowspan: 1, + backgroundColor: "default", + textColor: "blue", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["This spans 2 columns\nand 2 rows"], + props: { + colspan: 2, + rowspan: 2, + backgroundColor: "yellow", + textColor: "default", textAlignment: "left", + }, + }, + { + type: "tableCell", + content: ["Sooo many features"], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", textColor: "default", + textAlignment: "left", }, }, + ], + }, + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [], props: { - backgroundColor: "default", colspan: 1, rowspan: 1, + backgroundColor: "gray", + textColor: "purple", textAlignment: "left", - textColor: "default", }, }, ], @@ -1247,18 +2502,67 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["A cell"], props: { - backgroundColor: "default", colspan: 1, rowspan: 1, + backgroundColor: "default", + textColor: "default", textAlignment: "left", + }, + }, + { + type: "tableCell", + content: ["Another Cell"], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", textColor: "default", + textAlignment: "right", }, }, { type: "tableCell", - content: ["Table Cell"], + content: ["Aligned center"], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: hard breaks in cells + { + testCase: { + name: "table/hardBreakInCell", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Line 1\nLine 2", + styles: {}, + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1269,7 +2573,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Normal cell"], props: { backgroundColor: "default", colspan: 1, @@ -1280,11 +2584,28 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, ], }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: mixed text alignment per cell + { + testCase: { + name: "table/cellTextAlignment", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Left"], props: { backgroundColor: "default", colspan: 1, @@ -1295,23 +2616,23 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Center"], props: { backgroundColor: "default", colspan: 1, rowspan: 1, - textAlignment: "left", + textAlignment: "center", textColor: "default", }, }, { type: "tableCell", - content: ["Table Cell"], + content: ["Right"], props: { backgroundColor: "default", colspan: 1, rowspan: 1, - textAlignment: "left", + textAlignment: "right", textColor: "default", }, }, @@ -1399,6 +2720,44 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + testCase: { + name: "link/plainUrl", + content: [ + { + // id: UniqueID.options.generateID(), + type: "paragraph", + content: [ + { + type: "link", + href: "https://www.website.com", + content: "https://www.website.com", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "link/urlWithParens", + content: [ + { + // id: UniqueID.options.generateID(), + type: "paragraph", + content: [ + { + type: "link", + href: "https://en.wikipedia.org/wiki/Example_(disambiguation)", + content: "Example", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "hardbreak/basic", diff --git a/tests/src/unit/core/formatConversion/export/runTests.test.ts b/tests/src/unit/core/formatConversion/export/runTests.test.ts index 7ae6316112..e3abc58887 100644 --- a/tests/src/unit/core/formatConversion/export/runTests.test.ts +++ b/tests/src/unit/core/formatConversion/export/runTests.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { describe, it } from "vite-plus/test"; import { createTestEditor } from "../../createTestEditor.js"; import { testSchema } from "../../testSchema.js"; diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bold.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bold.json new file mode 100644 index 0000000000..0569abc8a9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bold.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/boldItalic.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/boldItalic.json new file mode 100644 index 0000000000..f6b0ca8045 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/boldItalic.json @@ -0,0 +1,22 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "Bold and italic", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bulletList.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bulletList.json new file mode 100644 index 0000000000..b5999d3ec9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bulletList.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/checkList.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/checkList.json new file mode 100644 index 0000000000..3f344bf122 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/checkList.json @@ -0,0 +1,38 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Unchecked", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Checked", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/codeBlock.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/codeBlock.json new file mode 100644 index 0000000000..63ebc503a5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/codeBlock.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = 42;", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/complexDocument.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/complexDocument.json new file mode 100644 index 0000000000..570c7e98d5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/complexDocument.json @@ -0,0 +1,106 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Title", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " text.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet 1", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet 2", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": undefined, + "id": "5", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "print('hello')", + "type": "text", + }, + ], + "id": "6", + "props": { + "language": "python", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/deeplyNestedLists.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/deeplyNestedLists.json new file mode 100644 index 0000000000..f81fde4a33 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/deeplyNestedLists.json @@ -0,0 +1,144 @@ +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Level 4 numbered", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 3 bullet", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 2 numbered", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Level 2 sibling", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 1 bullet", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Deep checklist item", + "type": "text", + }, + ], + "id": "8", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child of second bullet", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Another top-level bullet", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/defaultBlocks.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/defaultBlocks.json new file mode 100644 index 0000000000..69bef1f3f3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/defaultBlocks.json @@ -0,0 +1,474 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Welcome to this demo!", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Blocks:", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Heading", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Quote", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet List Item", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Numbered List Item", + "type": "text", + }, + ], + "id": "8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check List Item", + "type": "text", + }, + ], + "id": "9", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle List Item", + "type": "text", + }, + ], + "id": "10", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "console.log('Hello, world!');", + "type": "text", + }, + ], + "id": "11", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "12", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": undefined, + "id": "13", + "props": { + "backgroundColor": "default", + "caption": "From https://placehold.co/332x322.jpg", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://placehold.co/332x322.jpg", + }, + "type": "image", + }, + { + "children": [], + "content": undefined, + "id": "14", + "props": { + "backgroundColor": "default", + "caption": "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + }, + "type": "video", + }, + { + "children": [], + "content": undefined, + "id": "15", + "props": { + "backgroundColor": "default", + "caption": "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + "name": "", + "showPreview": true, + "url": "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + }, + "type": "audio", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Inline Content:", + "type": "text", + }, + ], + "id": "16", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "Styled Text", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "href": "https://www.blocknotejs.org", + "type": "link", + }, + ], + "id": "17", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/divider.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/divider.json new file mode 100644 index 0000000000..e944763aea --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/divider.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Before", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "After", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/hardBreak.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/hardBreak.json new file mode 100644 index 0000000000..5626739e88 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/hardBreak.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line 1 + Line 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/headingLevels.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/headingLevels.json new file mode 100644 index 0000000000..f1ce124a58 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/headingLevels.json @@ -0,0 +1,59 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 3", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/image.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/image.json new file mode 100644 index 0000000000..c9a6bddd61 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/image.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Example", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/inlineCode.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/inlineCode.json new file mode 100644 index 0000000000..6ddd0e59ee --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/inlineCode.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "code": true, + }, + "text": "Code text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/italic.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/italic.json new file mode 100644 index 0000000000..01ec89cd69 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/italic.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/link.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/link.json new file mode 100644 index 0000000000..fabbb2daa3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/link.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " more text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/mixedStyles.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/mixedStyles.json new file mode 100644 index 0000000000..7085f4c8b8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/mixedStyles.json @@ -0,0 +1,50 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Normal ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "strike", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/multipleParagraphs.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/multipleParagraphs.json new file mode 100644 index 0000000000..fc70b307a0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/multipleParagraphs.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "First paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second paragraph", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/nestedLists.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/nestedLists.json new file mode 100644 index 0000000000..90bb306cb8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/nestedLists.json @@ -0,0 +1,54 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/numberedList.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/numberedList.json new file mode 100644 index 0000000000..f2dfd7912e --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/numberedList.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/paragraph.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/paragraph.json new file mode 100644 index 0000000000..575bc9876a --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/paragraph.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Simple paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/quote.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/quote.json new file mode 100644 index 0000000000..7a2b3b4601 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/quote.json @@ -0,0 +1,18 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "A quote", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file 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 new file mode 100644 index 0000000000..0ede1c2000 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json @@ -0,0 +1,124 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Literal ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "asterisks", + "type": "text", + }, + { + "styles": {}, + "text": " and ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "double asterisks", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Backticks ` in plain text and ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "double", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Underscores ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "here", + "type": "text", + }, + { + "styles": {}, + "text": " and ~tildes~ and [brackets]", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Pipes | and backslash \ and #hash at start", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = `template ${literal}`; +const y = '```triple backticks```';", + "type": "text", + }, + ], + "id": "5", + "props": { + "language": "text", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/strike.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/strike.json new file mode 100644 index 0000000000..7f504a4b3f --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/strike.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strikethrough text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/table.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/table.json new file mode 100644 index 0000000000..7fb20ea004 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/table.json @@ -0,0 +1,97 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Header 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "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", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/tableWithHeaderRow.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/tableWithHeaderRow.json new file mode 100644 index 0000000000..7944f66a18 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/tableWithHeaderRow.json @@ -0,0 +1,97 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Header 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "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", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/video.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/video.json new file mode 100644 index 0000000000..57607adfdf --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/video.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Example", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/video.mp4", + }, + "type": "video", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts b/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts index 33d9b09c15..eb83a37e86 100644 --- a/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts +++ b/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts @@ -2,6 +2,7 @@ import { ExportParseEqualityTestCase } from "../../../shared/formatConversion/ex import { testExportParseEqualityBlockNoteHTML, testExportParseEqualityHTML, + testExportParseEqualityMarkdown, } from "../../../shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.js"; import { TestInstance } from "../../../types.js"; import { @@ -203,6 +204,62 @@ export const exportParseEqualityTestInstancesHTML: TestInstance< }, executeTest: testExportParseEqualityHTML, }, + { + testCase: { + name: "lists/toggleListItem", + content: [ + { + type: "toggleListItem", + content: "Toggle List Item", + }, + ], + }, + executeTest: testExportParseEqualityHTML, + }, + { + testCase: { + name: "lists/toggleListItemWithChildren", + content: [ + { + type: "toggleListItem", + content: "Toggle List Item", + children: [ + { + type: "paragraph", + content: "Child 1", + }, + { + type: "paragraph", + content: "Child 2", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityHTML, + }, + { + testCase: { + name: "lists/toggleHeading", + content: [ + { + type: "heading", + props: { + level: 2, + isToggleable: true, + }, + content: "Toggle Heading", + children: [ + { + type: "paragraph", + content: "Heading Child 1", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityHTML, + }, { testCase: { name: "tables/advanced", @@ -315,3 +372,724 @@ export const exportParseEqualityTestInstancesHTML: TestInstance< executeTest: testExportParseEqualityHTML, }, ]; + +// Markdown round-trip tests: blocks → markdown → blocks +// Markdown is a lossy format (no colors, underline, alignment), so these tests +// use snapshot matching to capture the expected round-trip result rather than +// strict equality with the input. This is critical for verifying that the +// custom markdown parser/serializer produces the same round-trip results. +export const exportParseEqualityTestInstancesMarkdown: TestInstance< + ExportParseEqualityTestCase< + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema + >, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = [ + { + testCase: { + name: "markdown/paragraph", + content: [ + { + type: "paragraph", + content: "Simple paragraph", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/multipleParagraphs", + content: [ + { + type: "paragraph", + content: "First paragraph", + }, + { + type: "paragraph", + content: "Second paragraph", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/headingLevels", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Heading 1", + }, + { + type: "heading", + props: { level: 2 }, + content: "Heading 2", + }, + { + type: "heading", + props: { level: 3 }, + content: "Heading 3", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/bulletList", + content: [ + { + type: "bulletListItem", + content: "Item 1", + }, + { + type: "bulletListItem", + content: "Item 2", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/numberedList", + content: [ + { + type: "numberedListItem", + content: "Item 1", + }, + { + type: "numberedListItem", + content: "Item 2", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/checkList", + content: [ + { + type: "checkListItem", + content: "Unchecked", + }, + { + type: "checkListItem", + props: { checked: true }, + content: "Checked", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/nestedLists", + content: [ + { + type: "bulletListItem", + content: "Parent", + children: [ + { + type: "numberedListItem", + content: "Child 1", + }, + { + type: "numberedListItem", + content: "Child 2", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/codeBlock", + content: [ + { + type: "codeBlock", + props: { language: "javascript" }, + content: "const x = 42;", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/divider", + content: [ + { + type: "paragraph", + content: "Before", + }, + { + type: "divider", + }, + { + type: "paragraph", + content: "After", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/bold", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold text", + styles: { bold: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/italic", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Italic text", + styles: { italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/strike", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Strikethrough text", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/inlineCode", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Code text", + styles: { code: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/boldItalic", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold and italic", + styles: { bold: true, italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/link", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Text ", + styles: {}, + }, + { + type: "link", + content: "Link", + href: "https://example.com", + }, + { + type: "text", + text: " more text", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/image", + content: [ + { + type: "image", + props: { + url: "https://example.com/image.png", + name: "Example", + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/video", + content: [ + { + type: "video", + props: { + url: "https://example.com/video.mp4", + name: "Example", + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/table", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: ["Header 1", "Header 2"], + }, + { + cells: ["Cell 1", "Cell 2"], + }, + ], + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + // Complementary check for https://github.com/TypeCellOS/BlockNote/issues/739: + // a table WITH a real header row must round-trip with the header preserved + // (i.e. non-empty headers must not be treated as the empty-header case). + testCase: { + name: "markdown/tableWithHeaderRow", + content: [ + { + type: "table", + content: { + type: "tableContent", + headerRows: 1, + rows: [ + { + cells: ["Header 1", "Header 2"], + }, + { + cells: ["Cell 1", "Cell 2"], + }, + ], + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/quote", + content: [ + { + type: "quote", + content: "A quote", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/hardBreak", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Line 1\nLine 2", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/mixedStyles", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Normal ", + styles: {}, + }, + { + type: "text", + text: "bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "italic ", + styles: { italic: true }, + }, + { + type: "text", + text: "strike", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/complexDocument", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Title", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Paragraph with ", + styles: {}, + }, + { + type: "text", + text: "bold", + styles: { bold: true }, + }, + { + type: "text", + text: " text.", + styles: {}, + }, + ], + }, + { + type: "bulletListItem", + content: "Bullet 1", + }, + { + type: "bulletListItem", + content: "Bullet 2", + }, + { + type: "divider", + }, + { + type: "codeBlock", + props: { language: "python" }, + content: "print('hello')", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/deeplyNestedLists", + content: [ + { + type: "bulletListItem", + content: "Level 1 bullet", + children: [ + { + type: "numberedListItem", + content: "Level 2 numbered", + children: [ + { + type: "bulletListItem", + content: "Level 3 bullet", + children: [ + { + type: "numberedListItem", + content: "Level 4 numbered", + }, + ], + }, + ], + }, + { + type: "numberedListItem", + content: "Level 2 sibling", + }, + ], + }, + { + type: "bulletListItem", + content: "Another top-level bullet", + children: [ + { + type: "bulletListItem", + content: "Child of second bullet", + children: [ + { + type: "checkListItem", + content: "Deep checklist item", + }, + ], + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/specialCharEscaping", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Literal *asterisks* and **double asterisks**", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Backticks ` in plain text and `` double ``", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Underscores _here_ and ~tildes~ and [brackets]", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Pipes | and backslash \\ and #hash at start", + styles: {}, + }, + ], + }, + { + type: "codeBlock", + props: { language: "" }, + // eslint-disable-next-line no-template-curly-in-string + content: + "const x = `template ${literal}`;\nconst y = '```triple backticks```';", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + // Mirrors the default-blocks demo (examples/01-basic/04-default-blocks) + // so we get a single round-trip snapshot covering every default block + // type. Markdown is lossy (colors/alignment/file blocks/toggle + // affordances are dropped), so the snapshot documents what survives. + // Image/video/audio captions are preserved via raw `
    ` HTML. + testCase: { + name: "markdown/defaultBlocks", + content: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Blocks:", + styles: { bold: true }, + }, + ], + }, + { + type: "paragraph", + content: "Paragraph", + }, + { + type: "heading", + content: "Heading", + }, + { + type: "heading", + props: { isToggleable: true }, + content: "Toggle Heading", + }, + { + type: "quote", + content: "Quote", + }, + { + type: "bulletListItem", + content: "Bullet List Item", + }, + { + type: "numberedListItem", + content: "Numbered List Item", + }, + { + type: "checkListItem", + content: "Check List Item", + }, + { + type: "toggleListItem", + content: "Toggle List Item", + }, + { + type: "codeBlock", + props: { language: "javascript" }, + content: "console.log('Hello, world!');", + }, + { + type: "table", + content: { + type: "tableContent", + rows: [ + { cells: ["Table Cell", "Table Cell", "Table Cell"] }, + { cells: ["Table Cell", "Table Cell", "Table Cell"] }, + { cells: ["Table Cell", "Table Cell", "Table Cell"] }, + ], + }, + }, + { + type: "file", + }, + { + type: "image", + props: { + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", + }, + }, + { + type: "video", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + }, + }, + { + type: "audio", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + }, + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline Content:", + styles: { bold: true }, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Styled Text", + styles: { + bold: true, + italic: true, + textColor: "red", + backgroundColor: "blue", + }, + }, + { + type: "text", + text: " ", + styles: {}, + }, + { + type: "link", + content: "Link", + href: "https://www.blocknotejs.org", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, +]; diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts b/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts index fc7f33f3c8..979574ab77 100644 --- a/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts +++ b/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts @@ -1,10 +1,11 @@ -import { describe, it } from "vitest"; +import { describe, it } from "vite-plus/test"; import { createTestEditor } from "../../createTestEditor.js"; import { testSchema } from "../../testSchema.js"; import { exportParseEqualityTestInstancesBlockNoteHTML, exportParseEqualityTestInstancesHTML, + exportParseEqualityTestInstancesMarkdown, } from "./exportParseEqualityTestInstances.js"; // Tests for verifying that exporting blocks to another format, then importing @@ -36,3 +37,16 @@ describe("Export/parse equality tests (HTML)", () => { }); } }); + +describe("Export/parse equality tests (Markdown)", () => { + const getEditor = createTestEditor(testSchema); + + for (const { + testCase, + executeTest, + } of exportParseEqualityTestInstancesMarkdown) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json index 40018a5ae2..0ee4579333 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json @@ -15,10 +15,7 @@ { "styles": {}, "text": "Table Cell -Table Cell - - Table Cell -", +Table Cell Table Cell", "type": "text", }, ], diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedToggleAndBulletList.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedToggleAndBulletList.json new file mode 100644 index 0000000000..93b62ea578 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedToggleAndBulletList.json @@ -0,0 +1,71 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet Item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Child", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle Item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Another Bullet", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/msWordPaste.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/msWordPaste.json new file mode 100644 index 0000000000..b7eb0a7de4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/msWordPaste.json @@ -0,0 +1,126 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "underline": true, + }, + "text": "Que se passe-t-il si je réponds tard à un message chat et que l'utilisateur n'est plus en ligne :", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Lorsque vous envoyez un message à un utilisateur dans une conversation chat, et qu'il est encore en ligne, il recevra le message sur sa bulle chatbot.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Cependant S'il n'est plus en ligne, votre message sera envoyé par email si :", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": ". l'utilisateur n'a pas lu votre réponse après 2 minutes", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": ". l'utilisateur n'est plus présent sur votre site web", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": " ", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Cela se fait automatiquement donc, lorsque nous répondons par chat, si l'utilisateur n'est plus là, Crisp renvoie le message alors par email et le canal de discussion se transforme en canal de discussion email. + + Il est possible aussi de créer une conversation email directement le profil de l'utilisateur (bouton bleu en haut à droite de la conversation)", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json index 7502bc6d5a..9f1da7de75 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json @@ -406,11 +406,11 @@ With Hard Break", "content": [ { "styles": {}, - "text": "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + "text": "https://placehold.co/800x540.png", "type": "text", }, ], - "href": "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + "href": "https://placehold.co/800x540.png", "type": "link", }, ], diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/standaloneDetailsSummary.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/standaloneDetailsSummary.json new file mode 100644 index 0000000000..5f0b0e6bb0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/standaloneDetailsSummary.json @@ -0,0 +1,54 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child paragraph 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child paragraph 2", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeading.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeading.json new file mode 100644 index 0000000000..4186324717 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeading.json @@ -0,0 +1,39 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading Child 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle Heading", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": true, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeadingWithoutChildren.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeadingWithoutChildren.json new file mode 100644 index 0000000000..4661a0b906 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeadingWithoutChildren.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Heading No Children", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": true, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItem.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItem.json new file mode 100644 index 0000000000..ab85ec82d4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItem.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Item 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithChildren.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithChildren.json new file mode 100644 index 0000000000..f3fdda6f01 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithChildren.json @@ -0,0 +1,54 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle Item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithImage.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithImage.json new file mode 100644 index 0000000000..1e47b38cf8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithImage.json @@ -0,0 +1,51 @@ +[ + { + "children": [ + { + "children": [], + "content": undefined, + "id": "2", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "http://localhost:3000/exampleURL", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text after image", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle with image", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentFormattedRuns.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentFormattedRuns.json new file mode 100644 index 0000000000..24ea75b41d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentFormattedRuns.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "strike", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentLinks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentLinks.json new file mode 100644 index 0000000000..bdaf169421 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentLinks.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": {}, + "text": "Link1", + "type": "text", + }, + ], + "href": "https://example1.com", + "type": "link", + }, + { + "content": [ + { + "styles": {}, + "text": "Link2", + "type": "text", + }, + ], + "href": "https://example2.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/backslashEscapes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/backslashEscapes.json new file mode 100644 index 0000000000..fbdb14c852 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/backslashEscapes.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "*not bold* [not a link] ~not strike~", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/bareAngleBrackets.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/bareAngleBrackets.json new file mode 100644 index 0000000000..b50ace7d9e --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/bareAngleBrackets.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "1 < 2 and 3 > 0", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlComment.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlComment.json new file mode 100644 index 0000000000..dde187fb49 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlComment.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Next paragraph.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlDiv.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlDiv.json new file mode 100644 index 0000000000..76c9d076e7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlDiv.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "A warning block.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlInterruptsParagraph.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlInterruptsParagraph.json new file mode 100644 index 0000000000..f0b3a0131d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlInterruptsParagraph.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Some text before.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "raw block Some text after.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteLazyContinuation.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteLazyContinuation.json new file mode 100644 index 0000000000..146a54b49b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteLazyContinuation.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "This is a quote + that continues here + and here too", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteMultiline.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteMultiline.json new file mode 100644 index 0000000000..669e44db40 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteMultiline.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two + Line three", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithCode.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithCode.json new file mode 100644 index 0000000000..b4a209b413 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithCode.json @@ -0,0 +1,30 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Quote with ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "inline code", + "type": "text", + }, + { + "styles": {}, + "text": " inside", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithLink.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithLink.json new file mode 100644 index 0000000000..8cd0e17218 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithLink.json @@ -0,0 +1,34 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Quote with ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "a link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " inside", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldOnly.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldOnly.json new file mode 100644 index 0000000000..0569abc8a9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldOnly.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldUnderscore.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldUnderscore.json new file mode 100644 index 0000000000..fc81db25f4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldUnderscore.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold with underscores", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListBasic.json new file mode 100644 index 0000000000..62136bf1c9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListBasic.json @@ -0,0 +1,56 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Unchecked item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Checked item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Another unchecked", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListMixed.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListMixed.json new file mode 100644 index 0000000000..4084a42458 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListMixed.json @@ -0,0 +1,55 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Regular bullet", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Checked item", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListNested.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListNested.json new file mode 100644 index 0000000000..ff5c84bf29 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListNested.json @@ -0,0 +1,57 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child checked", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child unchecked", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json new file mode 100644 index 0000000000..cf59869a6f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "console.log('Hello');", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "text", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockIndented.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockIndented.json new file mode 100644 index 0000000000..475525d84e --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockIndented.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = 1;", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "ts", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockPython.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockPython.json new file mode 100644 index 0000000000..78cd6179bf --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockPython.json @@ -0,0 +1,18 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "def hello(): + print("Hello, world!")", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "python", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json new file mode 100644 index 0000000000..1a656bd726 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "code with tildes", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "text", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithLanguage.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithLanguage.json new file mode 100644 index 0000000000..90eb554680 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithLanguage.json @@ -0,0 +1,18 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = 42; +console.log(x);", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithSpecialChars.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithSpecialChars.json new file mode 100644 index 0000000000..bdf7b585f8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithSpecialChars.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "
    +

    Hello **not bold**

    +
    ", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "html", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeSpanWithNewline.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeSpanWithNewline.json new file mode 100644 index 0000000000..b9b3e84746 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeSpanWithNewline.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "code": true, + }, + "text": "foo bar baz", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/complexDocument.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/complexDocument.json new file mode 100644 index 0000000000..972d1d02df --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/complexDocument.json @@ -0,0 +1,442 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Main Title", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "An introduction paragraph with ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " and ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": {}, + "text": " text.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Section 1", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "First bullet point", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Nested point", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Second bullet point", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "A notable quote", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Code Example", + "type": "text", + }, + ], + "id": "8", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "function hello() { + return "world"; +}", + "type": "text", + }, + ], + "id": "9", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, + { + "children": [], + "content": undefined, + "id": "10", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Section 2", + "type": "text", + }, + ], + "id": "11", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Step one", + "type": "text", + }, + ], + "id": "12", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Step two", + "type": "text", + }, + ], + "id": "13", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Step three", + "type": "text", + }, + ], + "id": "14", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Feature", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Status", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Bold", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Done", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Italic", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Done", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "15", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": undefined, + "id": "16", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Image", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Final paragraph with ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "a link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": ".", + "type": "text", + }, + ], + "id": "17", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/deeplyNestedLists.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/deeplyNestedLists.json new file mode 100644 index 0000000000..535c584553 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/deeplyNestedLists.json @@ -0,0 +1,73 @@ +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Level 4", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 3", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/emptyString.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/emptyString.json new file mode 100644 index 0000000000..45f0949abe --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/emptyString.json @@ -0,0 +1,13 @@ +[ + { + "children": [], + "content": [], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/escapedDelimiterInEmphasis.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/escapedDelimiterInEmphasis.json new file mode 100644 index 0000000000..219c8e869a --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/escapedDelimiterInEmphasis.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "*", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakBackslash.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakBackslash.json new file mode 100644 index 0000000000..e4ed206bcf --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakBackslash.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakMultiple.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakMultiple.json new file mode 100644 index 0000000000..359a3b1cbc --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakMultiple.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two + Line three", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakTwoSpaces.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakTwoSpaces.json new file mode 100644 index 0000000000..e4ed206bcf --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakTwoSpaces.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH1.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH1.json new file mode 100644 index 0000000000..27be2c1d52 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH1.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH2.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH2.json new file mode 100644 index 0000000000..2cb042a21b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH2.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH3.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH3.json new file mode 100644 index 0000000000..3e79e93fa0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH3.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 3", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH4.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH4.json new file mode 100644 index 0000000000..b5dffeaff8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH4.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 4", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 4, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH5.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH5.json new file mode 100644 index 0000000000..9f69aff1a8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH5.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 5", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 5, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH6.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH6.json new file mode 100644 index 0000000000..c01c6b3437 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH6.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 6", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 6, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingInternalPadding.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingInternalPadding.json new file mode 100644 index 0000000000..c61af59e1d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingInternalPadding.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "foo", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingThenCode.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingThenCode.json new file mode 100644 index 0000000000..d32797e0b3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingThenCode.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Code Section", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "x = 42", + "type": "text", + }, + ], + "id": "2", + "props": { + "language": "python", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingTrailingWhitespace.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingTrailingWhitespace.json new file mode 100644 index 0000000000..4a72b6b759 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingTrailingWhitespace.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "foo", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingWithInlineStyles.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingWithInlineStyles.json new file mode 100644 index 0000000000..61518fb6d7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingWithInlineStyles.json @@ -0,0 +1,52 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "Italic", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "Strike", + "type": "text", + }, + { + "styles": {}, + "text": " Heading", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleAsterisks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleAsterisks.json new file mode 100644 index 0000000000..6d2e457fd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleAsterisks.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph above", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph below", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleDashes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleDashes.json new file mode 100644 index 0000000000..6d2e457fd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleDashes.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph above", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph below", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleUnderscores.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleUnderscores.json new file mode 100644 index 0000000000..6d2e457fd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleUnderscores.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph above", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph below", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageAngleBracketUrl.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageAngleBracketUrl.json new file mode 100644 index 0000000000..8ecc4b0a3b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageAngleBracketUrl.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "alt", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageNestedBracketsAlt.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageNestedBracketsAlt.json new file mode 100644 index 0000000000..bd2ec4d1a0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageNestedBracketsAlt.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "alt [with] brackets", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithAlt.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithAlt.json new file mode 100644 index 0000000000..7f7ff95459 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithAlt.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Alt text for image", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/photo.jpg", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithTitle.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithTitle.json new file mode 100644 index 0000000000..a6279cc8a6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithTitle.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "alt text", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCode.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCode.json new file mode 100644 index 0000000000..7e02a1406d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCode.json @@ -0,0 +1,31 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "This has ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "inline code", + "type": "text", + }, + { + "styles": {}, + "text": " in it", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCodeWithSpecialChars.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCodeWithSpecialChars.json new file mode 100644 index 0000000000..8b77e45fc7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCodeWithSpecialChars.json @@ -0,0 +1,31 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Use ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "const x = 42;", + "type": "text", + }, + { + "styles": {}, + "text": " to declare", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlTag.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlTag.json new file mode 100644 index 0000000000..775c811913 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlTag.json @@ -0,0 +1,31 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Hello ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "world", + "type": "text", + }, + { + "styles": {}, + "text": "!", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlVoidTag.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlVoidTag.json new file mode 100644 index 0000000000..a617cae33f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlVoidTag.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one +line two.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlWithAttributes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlWithAttributes.json new file mode 100644 index 0000000000..839e11fbb7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlWithAttributes.json @@ -0,0 +1,47 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text with ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " and ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": ".", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineImage.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineImage.json new file mode 100644 index 0000000000..35a0bc23d1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineImage.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text before text after", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicOnly.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicOnly.json new file mode 100644 index 0000000000..01ec89cd69 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicOnly.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicUnderscore.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicUnderscore.json new file mode 100644 index 0000000000..3e39b28872 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicUnderscore.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic with underscores", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/lineBreaks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/lineBreaks.json new file mode 100644 index 0000000000..359a3b1cbc --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/lineBreaks.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two + Line three", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkAndText.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkAndText.json new file mode 100644 index 0000000000..4e6bd86d51 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkAndText.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Before ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " after", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkBasic.json new file mode 100644 index 0000000000..2d0b7cff77 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkBasic.json @@ -0,0 +1,25 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": {}, + "text": "Example", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkInParagraph.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkInParagraph.json new file mode 100644 index 0000000000..0509ca27e9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkInParagraph.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check out ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "this link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " for more info.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithStyledContent.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithStyledContent.json new file mode 100644 index 0000000000..17d11b4dde --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithStyledContent.json @@ -0,0 +1,27 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithTitle.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithTitle.json new file mode 100644 index 0000000000..aac8fd51d6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithTitle.json @@ -0,0 +1,25 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": {}, + "text": "example", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/listWithStyledItems.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/listWithStyledItems.json new file mode 100644 index 0000000000..95ff31e2d2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/listWithStyledItems.json @@ -0,0 +1,83 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strikethrough item", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item with ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "code", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedInlineContent.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedInlineContent.json new file mode 100644 index 0000000000..71ff4f8a21 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedInlineContent.json @@ -0,0 +1,78 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Normal ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "strike", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "code", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedListTypes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedListTypes.json new file mode 100644 index 0000000000..a877f90459 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedListTypes.json @@ -0,0 +1,90 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Numbered child", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Another numbered", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Bullet item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check child", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Another bullet", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleImages.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleImages.json new file mode 100644 index 0000000000..11f15e765d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleImages.json @@ -0,0 +1,30 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "First", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/first.png", + }, + "type": "image", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Second", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/second.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleParagraphs.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleParagraphs.json new file mode 100644 index 0000000000..de4db270fe --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleParagraphs.json @@ -0,0 +1,53 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "First paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second paragraph", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Third paragraph", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedBulletLists.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedBulletLists.json new file mode 100644 index 0000000000..d2d96963e9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedBulletLists.json @@ -0,0 +1,89 @@ +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Deep nested", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Nested 2", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Item 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 2", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasis.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasis.json new file mode 100644 index 0000000000..2e5c63d508 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasis.json @@ -0,0 +1,22 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "bold and italic", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasisComplex.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasisComplex.json new file mode 100644 index 0000000000..717e251a84 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasisComplex.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "bold ", + "type": "text", + }, + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "bold and italic", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": " bold", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedOrderedLists.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedOrderedLists.json new file mode 100644 index 0000000000..301593b562 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedOrderedLists.json @@ -0,0 +1,71 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Sub first", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Sub second", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "First", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/onlyWhitespace.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/onlyWhitespace.json new file mode 100644 index 0000000000..45f0949abe --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/onlyWhitespace.json @@ -0,0 +1,13 @@ +[ + { + "children": [], + "content": [], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/orderedListStart.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/orderedListStart.json new file mode 100644 index 0000000000..35d2df68a3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/orderedListStart.json @@ -0,0 +1,54 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Third item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "start": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Fourth item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Fifth item", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/paragraphLeadingIndent.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/paragraphLeadingIndent.json new file mode 100644 index 0000000000..613cf56ae2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/paragraphLeadingIndent.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "aaa + bbb", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH1.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH1.json new file mode 100644 index 0000000000..27be2c1d52 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH1.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH2.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH2.json new file mode 100644 index 0000000000..2cb042a21b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH2.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/singleNewLines.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/singleNewLines.json new file mode 100644 index 0000000000..3cd78e1f12 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/singleNewLines.json @@ -0,0 +1,22 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line 1 + Line 2 + Line 3 + Line 4", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/strikethroughOnly.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/strikethroughOnly.json new file mode 100644 index 0000000000..7f504a4b3f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/strikethroughOnly.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strikethrough text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableAlignment.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableAlignment.json new file mode 100644 index 0000000000..35d1354f9f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableAlignment.json @@ -0,0 +1,132 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Left", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Center", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Right", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "L", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "C", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "R", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableBasic.json new file mode 100644 index 0000000000..7a5d236441 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableBasic.json @@ -0,0 +1,135 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Header 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "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", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableFollowedByParagraph.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableFollowedByParagraph.json new file mode 100644 index 0000000000..36896f03df --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableFollowedByParagraph.json @@ -0,0 +1,114 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Col 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Col 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "A", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "B", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph after table", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tablePipeless.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tablePipeless.json new file mode 100644 index 0000000000..4052304874 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tablePipeless.json @@ -0,0 +1,97 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Col 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Col 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "A", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "B", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableThreeColumns.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableThreeColumns.json new file mode 100644 index 0000000000..e235519185 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableThreeColumns.json @@ -0,0 +1,132 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "A", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "B", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "C", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithInlineFormatting.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithInlineFormatting.json new file mode 100644 index 0000000000..7d0cb2bfe3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithInlineFormatting.json @@ -0,0 +1,141 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Styled", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Normal", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strike", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithLinks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithLinks.json new file mode 100644 index 0000000000..7ac6eab9fb --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithLinks.json @@ -0,0 +1,103 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Name", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Example", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "content": [ + { + "styles": {}, + "text": "Click", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json index 5070e1873e..55635684d9 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json @@ -6,7 +6,7 @@ "props": { "backgroundColor": "default", "caption": "", - "name": "", + "name": "Video", "showPreview": true, "textAlignment": "left", "url": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", diff --git a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts index d5ad737dd5..5bb72fec41 100644 --- a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts +++ b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts @@ -676,7 +676,7 @@ With Hard Break

  • Numbered List Item 2
  • Background Color Paragraph

    -

    !https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg

    +

    !https://placehold.co/800x540.png

    @@ -935,6 +935,96 @@ console.log("Third Line")`, testCase: { name: "divider", content: `
    ` }, executeTest: testParseHTML, }, + { + testCase: { + name: "toggleListItem", + content: `
      +
    • +
      +

      Toggle Item

      +
      +
    • +
    • +
      +

      Toggle Item 2

      +
      +
    • +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleListItemWithChildren", + content: `
      +
    • +
      +

      Toggle Item

      +

      Child 1

      +

      Child 2

      +
      +
    • +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleHeading", + content: `
    +

    Toggle Heading

    +

    Heading Child 1

    +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleHeadingWithoutChildren", + content: `
    +

    Toggle Heading No Children

    +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "standaloneDetailsSummary", + content: `
    + Toggle text +

    Child paragraph 1

    +

    Child paragraph 2

    +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "mixedToggleAndBulletList", + content: `
      +
    • Bullet Item
    • +
    • +
      +

      Toggle Item

      +

      Toggle Child

      +
      +
    • +
    • Another Bullet
    • +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleListItemWithImage", + content: `
    + Toggle with image + +

    Text after image

    +
    `, + }, + executeTest: testParseHTML, + }, { testCase: { name: "trailing whitespace", @@ -949,6 +1039,70 @@ console.log("Third Line")`, }, executeTest: testParseHTML, }, + { + testCase: { + name: "msWordPaste", + content: ` + + + + + + + + + + + + +

    Que se passe-t-il si je réponds tard à +un message chat et que l'utilisateur n'est plus en ligne :

    + +

    Lorsque vous envoyez un message à un +utilisateur dans une conversation chat, et qu'il est encore en ligne, il +recevra le message sur sa bulle chatbot.

    + +

    Cependant +S'il n'est plus en ligne, votre message sera envoyé par email si :

    + +

    . +l'utilisateur n'a pas lu votre réponse après 2 minutes

    + +

    . +l'utilisateur n'est plus présent sur votre site web

    + +

     

    + +

    Cela se fait automatiquement donc, lorsque +nous répondons par chat, si l'utilisateur n'est plus là, Crisp renvoie le +message alors par email et le canal de discussion se transforme en canal de +discussion email.
    +
    +Il est possible aussi de créer une conversation email directement le profil de +l'utilisateur (bouton bleu en haut à droite de la conversation)

    + + + + +`, + }, + executeTest: testParseHTML, + }, ]; export const parseTestInstancesMarkdown: TestInstance< @@ -1125,4 +1279,771 @@ Regular paragraph`, }, executeTest: testParseMarkdown, }, + // Individual heading levels + { + testCase: { + name: "headingH1", + content: `# Heading 1`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH2", + content: `## Heading 2`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH3", + content: `### Heading 3`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH4", + content: `#### Heading 4`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH5", + content: `##### Heading 5`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH6", + content: `###### Heading 6`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingWithInlineStyles", + content: `# **Bold** *Italic* ~~Strike~~ Heading`, + }, + executeTest: testParseMarkdown, + }, + // Setext headings + { + testCase: { + name: "setextH1", + content: `Heading 1 +===`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "setextH2", + content: `Heading 2 +---`, + }, + executeTest: testParseMarkdown, + }, + // Code blocks + { + testCase: { + name: "codeBlockBasic", + content: `\`\`\` +console.log('Hello'); +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockWithLanguage", + content: `\`\`\`javascript +const x = 42; +console.log(x); +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockPython", + content: `\`\`\`python +def hello(): + print("Hello, world!") +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockWithSpecialChars", + content: `\`\`\`html +
    +

    Hello **not bold**

    +
    +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockTildes", + content: `~~~ +code with tildes +~~~`, + }, + executeTest: testParseMarkdown, + }, + // Horizontal rules + { + testCase: { + name: "horizontalRuleDashes", + content: `Paragraph above + +--- + +Paragraph below`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "horizontalRuleAsterisks", + content: `Paragraph above + +*** + +Paragraph below`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "horizontalRuleUnderscores", + content: `Paragraph above + +___ + +Paragraph below`, + }, + executeTest: testParseMarkdown, + }, + // Inline code + { + testCase: { + name: "inlineCode", + content: `This has \`inline code\` in it`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "inlineCodeWithSpecialChars", + content: `Use \`const x = 42;\` to declare`, + }, + executeTest: testParseMarkdown, + }, + // Links + { + testCase: { + name: "linkBasic", + content: `[Example](https://example.com)`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "linkInParagraph", + content: `Check out [this link](https://example.com) for more info.`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "linkWithStyledContent", + content: `[**Bold link**](https://example.com)`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "adjacentLinks", + content: `[Link1](https://example1.com)[Link2](https://example2.com)`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "linkAndText", + content: `Before [Link](https://example.com) after`, + }, + executeTest: testParseMarkdown, + }, + // Tables + { + testCase: { + name: "tableBasic", + content: `| Header 1 | Header 2 | +| -------- | -------- | +| Cell 1 | Cell 2 | +| Cell 3 | Cell 4 |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableThreeColumns", + content: `| A | B | C | +| - | - | - | +| 1 | 2 | 3 |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableWithInlineFormatting", + content: `| Header | Styled | +| ------ | ------ | +| Normal | **Bold** | +| *Italic* | ~~Strike~~ |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableWithLinks", + content: `| Name | Link | +| ---- | ---- | +| Example | [Click](https://example.com) |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableAlignment", + content: `| Left | Center | Right | +| :--- | :----: | ----: | +| L | C | R |`, + }, + executeTest: testParseMarkdown, + }, + // Task lists / check lists + { + testCase: { + name: "checkListBasic", + content: `- [ ] Unchecked item +- [x] Checked item +- [ ] Another unchecked`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "checkListMixed", + content: `- Regular bullet +- [ ] Check item +- [x] Checked item`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "checkListNested", + content: `- [ ] Parent item + - [x] Child checked + - [ ] Child unchecked`, + }, + executeTest: testParseMarkdown, + }, + // Ordered list with start number + { + testCase: { + name: "orderedListStart", + content: `3. Third item +4. Fourth item +5. Fifth item`, + }, + executeTest: testParseMarkdown, + }, + // Hard breaks + { + testCase: { + name: "hardBreakBackslash", + content: `Line one\\ +Line two`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "hardBreakMultiple", + content: `Line one\\ +Line two\\ +Line three`, + }, + executeTest: testParseMarkdown, + }, + // Backslash escapes + { + testCase: { + name: "backslashEscapes", + content: `\\*not bold\\* \\[not a link\\] \\~not strike\\~`, + }, + executeTest: testParseMarkdown, + }, + // Escaped delimiter inside emphasis + { + testCase: { + name: "escapedDelimiterInEmphasis", + content: `*\\**`, + }, + executeTest: testParseMarkdown, + }, + // Nested emphasis + { + testCase: { + name: "nestedEmphasis", + content: `***bold and italic***`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "nestedEmphasisComplex", + content: `**bold *bold and italic* bold**`, + }, + executeTest: testParseMarkdown, + }, + // Individual styles + { + testCase: { + name: "boldOnly", + content: `**Bold text**`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "italicOnly", + content: `*Italic text*`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "strikethroughOnly", + content: `~~Strikethrough text~~`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "boldUnderscore", + content: `__Bold with underscores__`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "italicUnderscore", + content: `_Italic with underscores_`, + }, + executeTest: testParseMarkdown, + }, + // Mixed inline content + { + testCase: { + name: "mixedInlineContent", + content: `Normal **bold** *italic* ~~strike~~ \`code\` [link](https://example.com)`, + }, + executeTest: testParseMarkdown, + }, + // Multiple paragraphs + { + testCase: { + name: "multipleParagraphs", + content: `First paragraph + +Second paragraph + +Third paragraph`, + }, + executeTest: testParseMarkdown, + }, + // Empty content + { + testCase: { + name: "emptyString", + content: ``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "onlyWhitespace", + content: ` + + `, + }, + executeTest: testParseMarkdown, + }, + // Line breaks + { + testCase: { + name: "lineBreaks", + content: `Line one +Line two +Line three`, + }, + executeTest: testParseMarkdown, + }, + // Nested lists - complex + { + testCase: { + name: "nestedBulletLists", + content: `- Item 1 + - Nested 1 + - Deep nested + - Nested 2 +- Item 2`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "nestedOrderedLists", + content: `1. First + 1. Sub first + 2. Sub second +2. Second`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "mixedListTypes", + content: `- Bullet item + 1. Numbered child + 2. Another numbered +- Another bullet + - [ ] Check child`, + }, + executeTest: testParseMarkdown, + }, + // Blockquote with multiple blocks + { + testCase: { + name: "blockquoteMultiline", + content: `> Line one +> Line two +> Line three`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "blockquoteWithCode", + content: `> Quote with \`inline code\` inside`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "blockquoteWithLink", + content: `> Quote with [a link](https://example.com) inside`, + }, + executeTest: testParseMarkdown, + }, + // Blockquote with lazy continuation (no > on continuation lines) + { + testCase: { + name: "blockquoteLazyContinuation", + content: `> This is a quote +that continues here +and here too`, + }, + executeTest: testParseMarkdown, + }, + // Complex document + { + testCase: { + name: "complexDocument", + content: `# Main Title + +An introduction paragraph with **bold** and *italic* text. + +## Section 1 + +- First bullet point +- Second bullet point + - Nested point + +> A notable quote + +### Code Example + +\`\`\`javascript +function hello() { + return "world"; +} +\`\`\` + +--- + +## Section 2 + +1. Step one +2. Step two +3. Step three + +| Feature | Status | +| ------- | ------ | +| Bold | Done | +| Italic | Done | + +![Image](https://example.com/image.png) + +Final paragraph with [a link](https://example.com).`, + }, + executeTest: testParseMarkdown, + }, + // Image with alt text + { + testCase: { + name: "imageWithAlt", + content: `![Alt text for image](https://example.com/photo.jpg)`, + }, + executeTest: testParseMarkdown, + }, + // Multiple images + { + testCase: { + name: "multipleImages", + content: `![First](https://example.com/first.png) + +![Second](https://example.com/second.png)`, + }, + executeTest: testParseMarkdown, + }, + // Inline image within text (should be handled) + { + testCase: { + name: "inlineImage", + content: `Text before ![inline](https://example.com/img.png) text after`, + }, + executeTest: testParseMarkdown, + }, + // Code block immediately after heading + { + testCase: { + name: "headingThenCode", + content: `## Code Section + +\`\`\`python +x = 42 +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + // List with styled items + { + testCase: { + name: "listWithStyledItems", + content: `- **Bold item** +- *Italic item* +- ~~Strikethrough item~~ +- Item with \`code\``, + }, + executeTest: testParseMarkdown, + }, + // Deeply nested lists + { + testCase: { + name: "deeplyNestedLists", + content: `- Level 1 + - Level 2 + - Level 3 + - Level 4`, + }, + executeTest: testParseMarkdown, + }, + // Table followed by paragraph + { + testCase: { + name: "tableFollowedByParagraph", + content: `| Col 1 | Col 2 | +| ----- | ----- | +| A | B | + +Paragraph after table`, + }, + executeTest: testParseMarkdown, + }, + // Paragraphs with various inline formatting + { + testCase: { + name: "adjacentFormattedRuns", + content: `**bold***italic*~~strike~~`, + }, + executeTest: testParseMarkdown, + }, + // Table without outer pipes (GFM allows optional outer pipes) + { + testCase: { + name: "tablePipeless", + content: `Col 1 | Col 2 +----- | ----- +A | B`, + }, + executeTest: testParseMarkdown, + }, + // Indented fenced code block (up to 3 leading spaces per CommonMark) + { + testCase: { + name: "codeBlockIndented", + content: ` \`\`\`ts +const x = 1; + \`\`\``, + }, + executeTest: testParseMarkdown, + }, + // Link with title (title should not appear in href) + { + testCase: { + name: "linkWithTitle", + content: `[example](https://example.com "Example Site")`, + }, + executeTest: testParseMarkdown, + }, + // Image with nested brackets in alt text + { + testCase: { + name: "imageNestedBracketsAlt", + content: `![alt [with] brackets](https://example.com/image.png)`, + }, + executeTest: testParseMarkdown, + }, + // Inline raw HTML tag inside a paragraph passes through verbatim + { + testCase: { + name: "inlineHtmlTag", + content: `Hello world!`, + }, + executeTest: testParseMarkdown, + }, + // Multiple inline HTML tags with attributes + { + testCase: { + name: "inlineHtmlWithAttributes", + content: `Text with bold and link.`, + }, + executeTest: testParseMarkdown, + }, + // A self-closing-style void HTML tag inside a paragraph + { + testCase: { + name: "inlineHtmlVoidTag", + content: `Line one
    line two.`, + }, + executeTest: testParseMarkdown, + }, + // Block-level raw HTML is emitted verbatim — not wrapped in

    + { + testCase: { + name: "blockHtmlDiv", + content: `

    +A warning block. +
    `, + }, + executeTest: testParseMarkdown, + }, + // Block-level HTML comment + { + testCase: { + name: "blockHtmlComment", + content: ` + +Next paragraph.`, + }, + executeTest: testParseMarkdown, + }, + // Bare angle brackets that don't form a valid tag must still be escaped + { + testCase: { + name: "bareAngleBrackets", + content: `1 < 2 and 3 > 0`, + }, + executeTest: testParseMarkdown, + }, + // Block HTML interrupting a paragraph above it + { + testCase: { + name: "blockHtmlInterruptsParagraph", + content: `Some text before. +
    raw block
    +Some text after.`, + }, + executeTest: testParseMarkdown, + }, + // Hard line break via two trailing spaces (CommonMark ex. 633) + { + testCase: { + name: "hardBreakTwoSpaces", + content: `Line one \nLine two`, + }, + executeTest: testParseMarkdown, + }, + // ATX heading: closing #'s and trailing whitespace are stripped (ex. 73) + { + testCase: { + name: "headingTrailingWhitespace", + content: `### foo ### `, + }, + executeTest: testParseMarkdown, + }, + // ATX heading: lots of internal padding still produces a clean heading (ex. 67) + { + testCase: { + name: "headingInternalPadding", + content: `# foo `, + }, + executeTest: testParseMarkdown, + }, + // Code span with internal newline collapses to space (CommonMark ex. 337) + { + testCase: { + name: "codeSpanWithNewline", + content: "`foo bar \nbaz`", + }, + executeTest: testParseMarkdown, + }, + // Image with title attribute (CommonMark ex. 572). The title is parsed + // even if the BlockNote image block doesn't surface it as a prop — + // the point is to not leak `"title"` into the alt or src. + { + testCase: { + name: "imageWithTitle", + content: `![alt text](https://example.com/image.png "An image title")`, + }, + executeTest: testParseMarkdown, + }, + // Angle-bracket-wrapped image URL — brackets are stripped (ex. 580) + { + testCase: { + name: "imageAngleBracketUrl", + content: `![alt]()`, + }, + executeTest: testParseMarkdown, + }, + // Paragraph lines with up to 3 leading spaces of indent are still a + // paragraph; the indent is stripped (CommonMark ex. 222) + { + testCase: { + name: "paragraphLeadingIndent", + content: ` aaa\n bbb`, + }, + executeTest: testParseMarkdown, + }, ]; diff --git a/tests/src/unit/core/formatConversion/parse/runTests.test.ts b/tests/src/unit/core/formatConversion/parse/runTests.test.ts index 4e4d92b82b..a7d7c174d5 100644 --- a/tests/src/unit/core/formatConversion/parse/runTests.test.ts +++ b/tests/src/unit/core/formatConversion/parse/runTests.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { describe, it } from "vite-plus/test"; import { createTestEditor } from "../../createTestEditor.js"; import { testSchema } from "../../testSchema.js"; diff --git a/tests/src/unit/core/schema/__snapshots__/blocks.json b/tests/src/unit/core/schema/__snapshots__/blocks.json index 0bf3c107e4..142a5e7771 100644 --- a/tests/src/unit/core/schema/__snapshots__/blocks.json +++ b/tests/src/unit/core/schema/__snapshots__/blocks.json @@ -270,7 +270,11 @@ }, "node": null, "parse": [Function], + "parseContent": [Function], "render": [Function], + "runsBefore": [ + "toggleListItem", + ], "toExternalHTML": [Function], }, }, @@ -413,6 +417,7 @@ "render": [Function], "runsBefore": [ "default", + "heading", ], "toExternalHTML": [Function], }, @@ -564,7 +569,12 @@ "isolating": false, }, "node": null, + "parse": [Function], + "parseContent": [Function], "render": [Function], + "runsBefore": [ + "bulletListItem", + ], "toExternalHTML": [Function], }, }, diff --git a/tests/src/unit/core/schema/runTests.test.ts b/tests/src/unit/core/schema/runTests.test.ts index f14b0740de..4484491aac 100644 --- a/tests/src/unit/core/schema/runTests.test.ts +++ b/tests/src/unit/core/schema/runTests.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { createTestEditor } from "../createTestEditor.js"; import { testSchema } from "../testSchema.js"; diff --git a/tests/src/unit/core/selection/getSelection/runTests.test.ts b/tests/src/unit/core/selection/getSelection/runTests.test.ts index 37ae4363de..3dc127cda0 100644 --- a/tests/src/unit/core/selection/getSelection/runTests.test.ts +++ b/tests/src/unit/core/selection/getSelection/runTests.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { describe, it } from "vite-plus/test"; import { createTestEditor } from "../../createTestEditor.js"; import { testSchema } from "../../testSchema.js"; diff --git a/tests/src/unit/core/selection/incrementSelection/runTests.test.ts b/tests/src/unit/core/selection/incrementSelection/runTests.test.ts index 8f03a96cfa..6a12d4ef94 100644 --- a/tests/src/unit/core/selection/incrementSelection/runTests.test.ts +++ b/tests/src/unit/core/selection/incrementSelection/runTests.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { describe, it } from "vite-plus/test"; import { createTestEditor } from "../../createTestEditor.js"; import { testSchema } from "../../testSchema.js"; diff --git a/tests/src/unit/core/selection/textCursorPosition/runTests.test.ts b/tests/src/unit/core/selection/textCursorPosition/runTests.test.ts index ca145074db..fd2e7fd13a 100644 --- a/tests/src/unit/core/selection/textCursorPosition/runTests.test.ts +++ b/tests/src/unit/core/selection/textCursorPosition/runTests.test.ts @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { describe, it } from "vite-plus/test"; import { createTestEditor } from "../../createTestEditor.js"; import { testSchema } from "../../testSchema.js"; diff --git a/tests/src/unit/core/typeGuards/runTests.test.ts b/tests/src/unit/core/typeGuards/runTests.test.ts index a803e5f1e5..d4f7b1f408 100644 --- a/tests/src/unit/core/typeGuards/runTests.test.ts +++ b/tests/src/unit/core/typeGuards/runTests.test.ts @@ -1,5 +1,5 @@ import { BlockNoteEditor, editorHasBlockWithType } from "@blocknote/core"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from "vite-plus/test"; import { createTestEditor } from "../createTestEditor.js"; import { testSchema } from "../testSchema.js"; diff --git a/tests/src/unit/nextjs/serverUtil.test.ts b/tests/src/unit/nextjs/serverUtil.test.ts new file mode 100644 index 0000000000..b7fd1bf1da --- /dev/null +++ b/tests/src/unit/nextjs/serverUtil.test.ts @@ -0,0 +1,194 @@ +import { execSync, spawn, ChildProcess } from "child_process"; +import { getPort } from "get-port-please"; +import path from "path"; +import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test"; + +const TEST_APP_DIR = path.resolve(__dirname, "../../../nextjs-test-app"); +let PORT: number; +let BASE_URL: string; +const MODE = (process.env.NEXTJS_TEST_MODE || "dev") as "dev" | "build"; + +let nextProcess: ChildProcess; +let serverOutput = ""; +let serverErrors = ""; + +/** + * Regression test for #942: @blocknote/server-util must work in Next.js + * App Router server contexts (API routes) with serverExternalPackages. + * + * Set NEXTJS_TEST_MODE=build to test against a production build (slower + * but catches different issues). Defaults to dev mode for fast iteration. + */ +describe(`server-util in Next.js App Router (#942) [${MODE}]`, () => { + beforeAll(async () => { + PORT = await getPort({ portRange: [3900, 4100] }); + BASE_URL = `http://localhost:${PORT}`; + + // Pack and install @blocknote packages as tarballs + execSync("bash setup.sh", { + cwd: TEST_APP_DIR, + stdio: "pipe", + timeout: 240_000, + }); + + if (MODE === "build") { + // Build the Next.js app first + execSync("npx next build", { + cwd: TEST_APP_DIR, + stdio: "pipe", + timeout: 120_000, + }); + + // Start production server + nextProcess = spawn("npx", ["next", "start", "--port", String(PORT)], { + cwd: TEST_APP_DIR, + stdio: ["ignore", "pipe", "pipe"], + detached: true, + }); + } else { + // Start dev server with Turbopack + nextProcess = spawn( + "npx", + ["next", "dev", "--turbopack", "--port", String(PORT)], + { + cwd: TEST_APP_DIR, + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env, NODE_ENV: "development" }, + detached: true, + }, + ); + } + + // Wait for "Ready" message + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error(`Next.js ${MODE} server did not start within 60s`)); + }, 60_000); + + let stderr = ""; + + nextProcess.stdout?.on("data", (data: Buffer) => { + const text = data.toString(); + serverOutput += text; + if (text.includes("Ready")) { + clearTimeout(timeout); + resolve(); + } + }); + + nextProcess.stderr?.on("data", (data: Buffer) => { + stderr += data.toString(); + serverErrors += data.toString(); + }); + + nextProcess.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + + nextProcess.on("exit", (code) => { + if (code !== null && code !== 0) { + clearTimeout(timeout); + reject(new Error(`Next.js exited with code ${code}: ${stderr}`)); + } + }); + }); + }, 180_000); + + afterAll(async () => { + if (nextProcess?.pid == null) { + return; + } + + await new Promise((resolve) => { + nextProcess.once("exit", () => resolve()); + + try { + // Kill the entire process group so Next.js children don't linger + process.kill(-nextProcess.pid!, "SIGTERM"); + } catch { + // Process may have already exited + resolve(); + return; + } + + // Escalate to SIGKILL if still alive after 5s + setTimeout(() => { + try { + process.kill(-nextProcess.pid!, "SIGKILL"); + } catch { + // already gone + } + }, 5_000); + }); + }); + + it("ServerBlockNoteEditor works in API route (mirrors ReactServer.test.tsx)", async () => { + const res = await fetch(`${BASE_URL}/api/server-util`); + const text = await res.text(); + let body: any; + try { + body = JSON.parse(text); + } catch { + const nextDataMatch = text.match( + /