Abstracting and Rendering Rich Content with JSON
npm i @pcbowers/segments
Segments is yet another JSON based rich text specification. How it differs from other specifications is in its simplicity and extensibility. In fact, the specification itself requires very little and decisions are intentionally left to the discretion of its users.
A base segment has the following shape:
interface Segment {
_id: string;
_type: string;
children?: Segment[];
content?: Segment[];
mods?: string[];
modDefs?: {
_id: string;
_type: string;
[property: string]: unknown;
}[];
[property: string]: unknown;
}
A couple general rules are made apparent by the above structure:
- A segment is by nature recursive. Segments contain segments which contain segments. Thus, you are able to create a list item that contains bolded text along with a sub list with its on items.
- An underscore (
_
) prefixing any property represents a required property._id
and_type
are the only required properties based on this specification. - Additional properties are encouraged. If there are segments that require more information, the ability to add additional properties is baked into this specification as can be seen by the
[property: string]: unknown
property.
By taking these segments and placing them in an array ([Segment, Segment, ...]
), you can build almost any rich-text content system. Let's take a deeper dive into each property.
The _id
property represents a unique id for that segment. This allows different segments of the same type to be differentiated, and makes working with real-time interfaces easier.
This _id
property is special in that, while part of the specification and a required field, some implementations may make more sense to leave this property out. For instance, some segments may not need an identifier. Thus, while it is advised to use it, a JSON object without the _id
property on every segment can meet the requirements of this specification.
The _type
property represents the type of that segment. There are no predefined types: this is left as an implementation decision. However, some examples could include paragraph
, text
, list
, heading1
, and blockquote
.
The children
property is optional. This property allows you to differentiate children of a segment and content of a segment. For instance, a sub list may operate as a child of a list item. However, that list item also contains normal text. Thus, the children
property abstracts indirect content into its own property, allowing direct content to be placed in the content
property.
The content
property is optional, and contains more direct content that would not be placed on the children
property. Again, these optional properties are listed in the specification, but the fact that they are optional means they do not necessarily need to be used. The difference between children
and content
is left vague to support more use cases of this specification.
The mods
property is optional. It provides a simple way to add modifiers to your text. For example, the modifiers ['bold', 'italic']
would specify that all text in the segment should be bold and italic. In a way, the mods
property contains a list of types where their very definition is described by the type itself. However, if more information is needed, the property allows you to link a modifier definition using the associated _id
from the modDefs
array.
The modDefs
property is optional. It contains an array of modifier definitions. A modifier definition allows for more complex modifications than just simple text like bold
and italic
. Each definition requires an _id
to be used in the mods
property for identification and a _type
to identify what kind of modifier definition it is. No predefined modifier definitions are provided in this specification, though an example could be a link which would contain a _type
of link
and an additional property called href
with a url.
Examples often make more sense than explanations. Below are several examples of how segments can be used:
{
"_id": "6708c63d",
"_type": "paragraph",
"content": [
{
"_id": "a6e7de87",
"_type": "text",
"text": "I am a simple sentence with some "
},
{
"_id": "302bfb59",
"_type": "text",
"text": "bold",
"mods": ["bold"]
},
{
"_id": "57c305e0",
"_type": "text",
"text": " text. "
},
{
"_id": "d7267bf1",
"_type": "text",
"text": " Click Here",
"mods": ["2bd91da7"],
"modDefs": [
{
"_id": "2bd91da7",
"_type": "link",
"href": "https://github.com/pcbowers/segments",
"newTab": true
}
]
},
{
"_id": "662e57bf",
"_type": "text",
"text": "for more information."
}
]
}
Based on the example above, two segment types are used: paragraph
and text
. In fact, the text
segment, while not part of the specification, does provide the backbone of most segment implementations. The paragraph
segment contains a list of text segments that make up the following sentence:
I am a simple sentence with some bold text. Click Here for more information.
[
{
"_id": "bd05a896",
"_type": "list",
"type": "number",
"start": 5,
"content": [
{
"_id": "a34c7e21",
"_type": "text",
"text": "List Item 5"
}
]
},
{
"_id": "4aa415ed",
"_type": "list",
"type": "number",
"start": 5,
"content": [
{
"_id": "3185f99a",
"_type": "text",
"text": "List Item 6"
}
],
"children": [
{
"_id": "f3361d24",
"_type": "list",
"type": "lowerletter",
"content": [
{
"_id": "e2144fd3",
"_type": "text",
"text": "Sub Item a"
}
]
},
{
"_id": "e2bbb879",
"_type": "list",
"type": "lowerletter",
"content": [
{
"_id": "ba22c4d8",
"_type": "text",
"text": "Sub Item b"
}
]
}
]
}
]
This is an example of a list using segments. As you can see, it uses the type
property and the start
property to dictate list type and where to start in the list respectively. The list itself contains two items with the second item containing a sub list with an additional two items. The list would render to something like this:
- List Item 5
- List Item 6
- Sub Item a
- Sub Item b
Converting segments to rich text
TODO
Converting rich text to segments
TODO
TODO
TODO
TODO