Document Hierarchy

Each Hypermedia document has a hierarchy of blocks, where any block can have a list of children to represent conent "within" it.

Document Structure

A document has "children" which are a list of BlockNodes. Each BlockNode has:
block - A single block value
children - Another list of BlockNodes

Block Definitions

Each Block has:
id - short string, usually 8 characters of randomized a-zA-Z0-9_
type - Designate the block type which may each define additional fields
Any Block that refers directly to an URL will have:
ref - The Hypermedia or IPFS URL of the resource

Text Blocks

Many fields represent text and share the common attributes

text field

The raw text or content that will appear in the document

attributes field: TextAttributes

TextAttribute
An attribute is an object which may contain key-value pairs according to the block type description.

annotations field: Array<TextAnnotation>

TextAnnotation
An annotation is an object that adds functionality to a range of text in the block. Each annotation contains:
type - The type of annotation
starts - Array of numbers where the annotation starts
ends - Array of numbers where the annotation ends.
strong annotation
{
  type: 'strong',
  starts: [0],
  ends: [14]
}
emphasis annotation
{
  type: 'emphasis',
  starts: [0],
  ends: [14]
}
underline annotation
{
  type: 'underline',
  starts: [0],
  ends: [14]
}
strikethrough annotation
{
  type: 'strikethrough',
  starts: [0],
  ends: [14]
}
code annotation
{
  type: 'code',
  starts: [0],
  ends: [14]
}
link annotation
{
  type: 'link',
  starts: [0],
  ends: [14],
  ref: 'https://example.com' // 'hm://weoiruwoeiru?v=aldkjalksdjal'
}
color annotation
{
  type: 'color',
  starts: [0],
  ends: [14],
  attributes: {
    color: '#f00'
  }
}

paragraph - Text Block

A Text Block which is "normal" text, when type: paragraph is set
text field, the raw value of the text
attributes field, TextAttributes
annotations field, array of TextAnnotations

heading - Heading Block

A Text Block which represents a heading to organize content further down the hierarchy

code - Code Block

equation - Equation Block

Media Blocks

image - Image Block

video - Video Block

file - File Block

Reference blocks

embed - Embed Block

web-embed - Web Embed Block

currently only X (twitter) content is supported

nostr - Nostr Block

Updating Documents

A update to a document entity contains an array of DocumentChange objects.

DocumentChange

setTitle DocumentOperation

When change.case is setTitle, change the title of the Document.
new DocumentChange({
  op: {
    case: 'setTitle',
    value: title,
  },
})

moveBlock DocumentChange

new DocumentChange({
  op: {
    case: 'moveBlock',
    value: {
      blockId: 'a-zA-Z0-9_',
      leftSibling: '37as897s',
      parent: 'vi098smp',
    }
  },
})
To create or insert new blocks to the document, blocks must be moved into it.
operation.value.blockId - What blockId will be placed or moved
operation.value.leftSibling - What blockId appears before this block within the same doc hierarchy level. Empty if it is the first block
operation.value.parent - What is the parent blockId. Empty if it is a top-level block in the document

replaceBlock DocumentChange

new DocumentChange({
  op: {
    case: 'replaceBlock',
    value: {
      id: 'a-zA-Z0-9_',
      type: 'paragraph',
      text: 'Hello world',
      annotations: [],
      attributes: {
        childrenType: 'group'
      }
    }
  },
})
The value of the replaceBlock operation is the whole block, including the id. Each new block must be replaced right after it is moved into place with moveBlock in order to add its content.
operation.value.id - The blockId
operation.value.type - The block type. This is important only for the visual representation. For the backend, this attribute is a opaque string.
operation.value - The value is the new block values: text, attributes, annotations. Including the Id

deleteBlock DocumentChange

new DocumentChange({
  op: {
    case: 'deleteBlock',
    value: 'a-zA-Z0-9_'
  },
})
To remove a block from the document hierarchy, you use this DocumentChange object specifying only the blockId you want to remove.
There are two special things we currently do with deletions that might not be necessary, we do it just in case:
1.
send all the deletion changes at the end of the changes array: This ensures that if there was previous operations (replaceBlock usually) that affect the deleted block, those will be ignored.
2.
send all the side effect operations that this deletion caused: If we delete a block with children, the children should be moved the the level the deleted block was, which triggers multiple moveBlock changes for each children.