Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,29 @@ To run the project, open the command line in the project's root directory and en
pnpm install

# Start the example project
pnpm start
pnpm dev
```

## Commands

All commands are run from the project root with [`pnpm`](https://pnpm.io), which
wraps the `vp` ([vite-plus](https://vite-plus.dev)) task runner. The ones you'll
use day to day:

| Command | Description |
| ---------------- | ---------------------------------------------------------- |
| `pnpm install` | Install all dependencies. |
| `pnpm dev` | Start the example editor with live reload. |
| `pnpm start` | Build the packages, then preview the example editor. |
| `pnpm test` | Run the unit tests across all packages. |
| `pnpm lint` | Lint and type-check the codebase. Run this before pushing. |
| `pnpm run check` | Auto-fix lint and formatting issues across the project. |
| `pnpm build` | Build all packages. |
| `pnpm e2e` | Run the Playwright end-to-end tests. |

To run the unit tests for a single package, run `pnpm test` from inside that
package's directory; append `-u` to update snapshots.

## Adding packages

- Add the dependency to the relevant `package.json` file (packages/xxx/package.json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function setTextCursorPosition(

const info = getBlockInfo(posInfo);

const contentType: "none" | "inline" | "table" =
const contentType: "none" | "inline" | "table" | "plain" =
schema.blockSchema[info.blockNoteType]!.content;

if (info.isBlockContainer) {
Expand All @@ -81,7 +81,7 @@ export function setTextCursorPosition(
return;
}

if (contentType === "inline") {
if (contentType === "inline" || contentType === "plain") {
if (placement === "start") {
tr.setSelection(
TextSelection.create(tr.doc, blockContent.beforePos + 1),
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/api/nodeConversions/nodeToBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,11 @@ export function nodeToBlock<
inlineContentSchema,
styleSchema,
);
} else if (blockConfig.content === "plain") {
if (!blockInfo.isBlockContainer) {
throw new Error("impossible");
}
content = blockInfo.blockContent.node.textContent;
} else if (blockConfig.content === "none") {
content = undefined;
} else {
Expand Down
14 changes: 6 additions & 8 deletions packages/core/src/blocks/Code/block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe("Code block input rule", () => {

const block = editor.document[0];
expect(block.type).toBe("codeBlock");
expect(block.content).toEqual([]);
expect(block.content).toBe("");
});

it("converts ```ts + Enter into a codeBlock", () => {
Expand All @@ -135,7 +135,7 @@ describe("Code block input rule", () => {
const block = editor.document[0];
expect(block.type).toBe("codeBlock");
expect((block.props as any).language).toBe("ts");
expect(block.content).toEqual([]);
expect(block.content).toBe("");
});

it("converts ``` + Enter into a codeBlock with empty language", () => {
Expand Down Expand Up @@ -186,9 +186,8 @@ describe("Code block input rule", () => {
const after = editor.document[0];
expect(after.type).toBe("codeBlock");
expect(after.id).toBe(block.id);
expect(
(after.content as Array<{ type: string; text: string }>)[0].text,
).toBe("hello");
// The code block holds plain (string) content.
expect(after.content).toBe("hello");
});

it("places cursor inside the new code block after Enter conversion", () => {
Expand All @@ -205,9 +204,8 @@ describe("Code block input rule", () => {
const after = editor.document[0];
expect(after.type).toBe("codeBlock");
expect(after.id).toBe(block.id);
expect(
(after.content as Array<{ type: string; text: string }>)[0].text,
).toBe("world");
// The code block holds plain (string) content.
expect(after.content).toBe("world");
});

it("Enter inside an existing code block does not retrigger conversion", () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/blocks/Code/block.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { HighlighterGeneric } from "@shikijs/types";
import { DOMParser } from "@tiptap/pm/model";
import { createExtension } from "../../editor/BlockNoteExtension.js";
import { createBlockConfig, createBlockSpec } from "../../schema/index.js";
import { lazyShikiPlugin } from "./shiki.js";
import { DOMParser } from "@tiptap/pm/model";

export type CodeBlockOptions = {
/**
Expand Down Expand Up @@ -62,7 +62,7 @@ export const createCodeBlockConfig = createBlockConfig(
default: defaultLanguage,
},
},
content: "inline",
content: "plain",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}) as const,
);

Expand Down
42 changes: 32 additions & 10 deletions packages/core/src/schema/blocks/createSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function applyNonSelectableBlockFix(nodeView: NodeView, editor: Editor) {
export function getParseRules<
TName extends string,
TProps extends PropSchema,
TContent extends "inline" | "none" | "table",
TContent extends "inline" | "none" | "table" | "plain",
>(
config: BlockConfig<TName, TProps, TContent>,
implementation: BlockImplementation<TName, TProps, TContent>,
Expand Down Expand Up @@ -75,7 +75,9 @@ export function getParseRules<
// Because we do the parsing ourselves, we want to preserve whitespace for content we've parsed
preserveWhitespace: true,
getContent:
config.content === "inline" || config.content === "none"
config.content === "inline" ||
config.content === "none" ||
config.content === "plain"
? (node, schema) => {
if (implementation.parseContent) {
const result = implementation.parseContent({
Expand All @@ -89,6 +91,13 @@ export function getParseRules<
}
}

if (config.content === "plain") {
// Plain blocks hold unstyled text only, so we parse the
// element's text content directly into a single text node.
const text = (node as HTMLElement).textContent ?? "";
return text ? Fragment.from(schema.text(text)) : Fragment.empty;
Comment thread
YousefED marked this conversation as resolved.
Outdated
}

if (config.content === "inline") {
// Parse the inline content if it exists
const element = node as HTMLElement;
Expand Down Expand Up @@ -140,7 +149,7 @@ export function getParseRules<
export function addNodeAndExtensionsToSpec<
TName extends string,
TProps extends PropSchema,
TContent extends "inline" | "none" | "table",
TContent extends "inline" | "none" | "table" | "plain",
>(
blockConfig: BlockConfig<TName, TProps, TContent>,
blockImplementation: BlockImplementation<TName, TProps, TContent>,
Expand All @@ -153,9 +162,18 @@ export function addNodeAndExtensionsToSpec<
name: blockConfig.type,
content: (blockConfig.content === "inline"
? "inline*"
: blockConfig.content === "none"
? ""
: blockConfig.content) as TContent extends "inline" ? "inline*" : "",
: blockConfig.content === "plain"
? "text*"
Comment thread
YousefED marked this conversation as resolved.
: blockConfig.content === "none"
? ""
: blockConfig.content) as TContent extends "inline"
? "inline*"
: TContent extends "plain"
? "text*"
: "",
// "plain" blocks hold unstyled text only, so disallow marks on them.
// TODO: this might need to allow marks for comments and suggestions / diffs
marks: blockConfig.content === "plain" ? "" : undefined,
group: "blockContent",
selectable: blockImplementation.meta?.selectable ?? true,
isolating: blockImplementation.meta?.isolating ?? true,
Expand All @@ -180,7 +198,11 @@ export function addNodeAndExtensionsToSpec<
return wrapInBlockStructure(
{
dom: div,
contentDOM: blockConfig.content === "inline" ? div : undefined,
contentDOM:
blockConfig.content === "inline" ||
blockConfig.content === "plain"
? div
: undefined,
},
blockConfig.type,
{},
Expand Down Expand Up @@ -304,7 +326,7 @@ export function createBlockConfig<
export function createBlockSpec<
const TName extends string,
const TProps extends PropSchema,
const TContent extends "inline" | "none",
const TContent extends "inline" | "none" | "plain",
const TOptions extends Partial<Record<string, any>> | undefined = undefined,
>(
blockConfigOrCreator: BlockConfig<TName, TProps, TContent>,
Expand All @@ -323,7 +345,7 @@ export function createBlockSpec<
export function createBlockSpec<
const TName extends string,
const TProps extends PropSchema,
const TContent extends "inline" | "none",
const TContent extends "inline" | "none" | "plain",
const BlockConf extends BlockConfig<TName, TProps, TContent>,
const TOptions extends Partial<Record<string, any>>,
>(
Expand All @@ -349,7 +371,7 @@ export function createBlockSpec<
export function createBlockSpec<
const TName extends string,
const TProps extends PropSchema,
const TContent extends "inline" | "none",
const TContent extends "inline" | "none" | "plain",
const TOptions extends Partial<Record<string, any>> | undefined = undefined,
>(
blockConfigOrCreator: BlockConfigOrCreator<TName, TProps, TContent, TOptions>,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/schema/blocks/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export function createBlockSpecFromTiptapNode<
const T extends {
node: Node;
type: string;
content: "inline" | "table" | "none";
content: "inline" | "table" | "none" | "plain";
},
P extends PropSchema,
>(
Expand Down
72 changes: 52 additions & 20 deletions packages/core/src/schema/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ export interface BlockConfigMeta {
export interface BlockConfig<
T extends string = string,
PS extends PropSchema = PropSchema,
C extends "inline" | "none" | "table" = "inline" | "none" | "table",
C extends "inline" | "none" | "table" | "plain" =
| "inline"
| "none"
| "table"
| "plain",
> {
/**
* The type of the block (unique identifier within a schema)
Expand All @@ -93,7 +97,7 @@ export interface BlockConfig<
export type BlockConfigOrCreator<
TName extends string = string,
TProps extends PropSchema = PropSchema,
TContent extends "inline" | "none" = "inline" | "none",
TContent extends "inline" | "none" | "plain" = "inline" | "none" | "plain",
TOptions extends Record<string, any> | undefined =
| Record<string, any>
| undefined,
Expand All @@ -108,8 +112,10 @@ export type BlockConfigOrCreator<
*/
export type ExtractBlockConfigFromConfigOrCreator<
ConfigOrCreator extends
| BlockConfig<string, PropSchema, "inline" | "none">
| ((...args: any[]) => BlockConfig<string, PropSchema, "inline" | "none">),
| BlockConfig<string, PropSchema, "inline" | "none" | "plain">
| ((
...args: any[]
) => BlockConfig<string, PropSchema, "inline" | "none" | "plain">),
> = ConfigOrCreator extends (...args: any[]) => infer Config
? Config
: ConfigOrCreator;
Expand All @@ -118,14 +124,18 @@ export type ExtractBlockConfigFromConfigOrCreator<
export type CustomBlockConfig<
T extends string = string,
PS extends PropSchema = PropSchema,
C extends "inline" | "none" = "inline" | "none",
C extends "inline" | "none" | "plain" = "inline" | "none" | "plain",
> = BlockConfig<T, PS, C>;

// A Spec contains both the Config and Implementation
export type BlockSpec<
T extends string = string,
PS extends PropSchema = PropSchema,
C extends "inline" | "none" | "table" = "inline" | "none" | "table",
C extends "inline" | "none" | "table" | "plain" =
| "inline"
| "none"
| "table"
| "plain",
> = {
config: BlockConfig<T, PS, C>;
implementation: BlockImplementation<T, PS, C>;
Expand All @@ -139,7 +149,11 @@ export type BlockSpec<
export type BlockSpecOrCreator<
T extends string = string,
PS extends PropSchema = PropSchema,
C extends "inline" | "none" | "table" = "inline" | "none" | "table",
C extends "inline" | "none" | "table" | "plain" =
| "inline"
| "none"
| "table"
| "plain",
TOptions extends Record<string, any> | undefined =
| Record<string, any>
| undefined,
Expand All @@ -154,8 +168,10 @@ export type BlockSpecOrCreator<
*/
export type ExtractBlockSpecFromSpecOrCreator<
SpecOrCreator extends
| BlockSpec<string, PropSchema, "inline" | "none">
| ((...args: any[]) => BlockSpec<string, PropSchema, "inline" | "none">),
| BlockSpec<string, PropSchema, "inline" | "none" | "plain">
| ((
...args: any[]
) => BlockSpec<string, PropSchema, "inline" | "none" | "plain">),
> = SpecOrCreator extends (...args: any[]) => infer Spec ? Spec : SpecOrCreator;

/**
Expand All @@ -167,7 +183,11 @@ export type ExtractBlockSpecFromSpecOrCreator<
export type LooseBlockSpec<
T extends string = string,
PS extends PropSchema = PropSchema,
C extends "inline" | "none" | "table" = "inline" | "none" | "table",
C extends "inline" | "none" | "table" | "plain" =
| "inline"
| "none"
| "table"
| "plain",
> = {
config: BlockConfig<T, PS, C>;
implementation: Omit<
Expand Down Expand Up @@ -336,9 +356,11 @@ export type BlockFromConfigNoChildren<
? InlineContent<I, S>[]
: B["content"] extends "table"
? TableContent<I, S>
: B["content"] extends "none"
? undefined
: never;
: B["content"] extends "plain"
? string
: B["content"] extends "none"
? undefined
: never;
};

export type BlockFromConfig<
Expand Down Expand Up @@ -420,9 +442,11 @@ type PartialBlockFromConfigNoChildren<
? PartialInlineContent<I, S>
: B["content"] extends "table"
? PartialTableContent<I, S>
: B["content"] extends "none"
? undefined
: never;
: B["content"] extends "plain"
? string
: B["content"] extends "none"
? undefined
: never;
};

type PartialBlocksWithoutChildren<
Expand Down Expand Up @@ -472,7 +496,11 @@ export type BlockIdentifier = { id: string } | string;
export type BlockImplementation<
TName extends string = string,
TProps extends PropSchema = PropSchema,
TContent extends "inline" | "none" | "table" = "inline" | "none" | "table",
TContent extends "inline" | "none" | "table" | "plain" =
| "inline"
| "none"
| "table"
| "plain",
> = {
/**
* Metadata
Expand Down Expand Up @@ -589,10 +617,14 @@ export type BlockImplementationOrCreator<
*/
export type ExtractBlockImplementationFromImplementationOrCreator<
ImplementationOrCreator extends
| BlockImplementation<string, PropSchema, "inline" | "none">
| BlockImplementation<string, PropSchema, "inline" | "none" | "plain">
| ((
...args: any[]
) => BlockImplementation<string, PropSchema, "inline" | "none">),
) => BlockImplementation<
string,
PropSchema,
"inline" | "none" | "plain"
>),
> = ImplementationOrCreator extends (...args: any[]) => infer Implementation
? Implementation
: ImplementationOrCreator;
Expand All @@ -601,5 +633,5 @@ export type ExtractBlockImplementationFromImplementationOrCreator<
export type CustomBlockImplementation<
T extends string = string,
PS extends PropSchema = PropSchema,
C extends "inline" | "none" = "inline" | "none",
C extends "inline" | "none" | "plain" = "inline" | "none" | "plain",
> = BlockImplementation<T, PS, C>;
Loading
Loading