Skip to content

Lexicons

Nick The Sick
Mar 28, 20263 min read

his post is in reaction to @pfrazee.com here: https://bsky.app/profile/pfrazee.com/post/3mfd4onkpkk2o

BlockNote, is an extensible rich text editor with a structure very similar to what Paul was describing:

It is extensible, meaning that it can offer the addition of new block types; and, even swapping out of the schema entirely.

So, given this, there should be a very straightforward mapping from BlockNote’s JSON to a lexicon, so just as a proof of the concept - I made that mapping. This allows a BlockNote document to be represented as a lexicon & convert to and from the format (essentially a 1:1 mapping since they are so similar).

I actually see this lexicon as sort of being the foundation for a new type of rich text editor, which can have community-driven embeds, in a marketplace.

Take a look at this Typepec for BlockNote for more: https://github.com/typespecs/BlockNote/blob/main/lib/main.tsp

In BlockNote, we de-couple the schema from the document content, this allows us to be agnostic of the implementation of our blocks (content types like paragraphs, code-blocks), while still having a regular structure that we can use to understand the document’s content in a consistent way. Let’s look at an example of the schema that this very document relies on: a paragraph block:

interface ParagraphBlock {
  id: string;
  type: "paragraph";
  props: {
    backgroundColor: string;
    textColor: string;
    textAlignment: "left" | "center" | "right" | "justify";
  };
  content: InlineContent[];
  children: Block[];
};

Or, more generically:

type Block = {
  id: string;
  type: string;
  props: Record<string, boolean | number | string>;
  content: InlineContent[] | TableContent | undefined;
  children: Block[];
};

type Link = {
  type: "link";
  content: StyledText[];
  href: string;
};

type StyledText = {
  type: "text";
  text: string;
  styles: Styles;
};

type CustomInlineContent = {
  type: string;
  content: StyledText[] | undefined;
  props: Record<string, boolean | number | string>;
};

type InlineContent = Link | StyledText | CustomInlineContent;

// tables are special cased, for now
type TableContent = {
  type: "tableContent";
  columnWidths: (number | undefined)[];
  headerRows?: number;
  headerCols?: number;
  rows: {
    cells: TableCell[];
  }[];
};

type TableCell = {
  type: "tableCell";
  props: {
    backgroundColor: string;
    textColor: string;
    textAlignment: "left" | "center" | "right" | "justify";
    colspan?: number;
    rowspan?: number;
  };
  content: InlineContent[];
};

Too abstract? Let’s see a BlockNote document:

[
  {
    "id": "502a74dd-55ba-4637-9e78-9e355fc01469",
    "type": "paragraph",
    "props": {
      "backgroundColor": "default",
      "textColor": "default",
      "textAlignment": "left"
    },
    "content": [
      {
        "type": "text",
        "text": "Hello world",
        "styles": {}
      }
    ],
    "children": []
  },
  {
    "id": "721f2779-fc08-4354-99b6-6031c722cdfe",
    "type": "heading",
    "props": {
      "backgroundColor": "default",
      "textColor": "default",
      "textAlignment": "left",
      "level": 1,
      "isToggleable": false
    },
    "content": [
      {
        "type": "text",
        "text": "Heading",
        "styles": {}
      }
    ],
    "children": []
  },
  {
    "id": "13a9c876-9809-44c9-8b37-ccba066dad10",
    "type": "paragraph",
    "props": {
      "backgroundColor": "default",
      "textColor": "default",
      "textAlignment": "left"
    },
    "content": [],
    "children": []
  }
]

Want to the see the mapping of this very document? Head on over to it on pdsls!

Did you enjoy this article?

Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.

Across the AtmosphereDiscussions