Guides

Collections: an ordered list of Blocks

The collection Block type: its items and stored query, the node view with reorder and „Aus Abfrage füllen“, the block action that adds a Block, and the Markdown export.

A collection is a Typed block of the Block type collection that lists other Blocks, in an order the user sets. It points at its Blocks and holds none of them: removing an entry, or the collection, never touches the Block. A reading list, the Blocks of a chapter that live in several Documents, or the hits of a query a person curated are collections.

defaultBlockTypes(t) lists the type (label „Sammlung“ / "Collection", icon i-lucide-list-checks, capabilities container with 'all', the colour basket, properties and export), so a host that spreads the built-in list has it.

What a collection stores

Two Block properties in the Typed block's meta:

KeyTypeMeaning
itemsreference-listthe entries, in order: BlockAddress[] ({ documentId, blockId }; documentId '' is a Block of this Document)
querytext, hiddenan optional DataQuery as JSON text, for „Aus Abfrage füllen“; the properties form never shows it

The framework-free functions in @skaile/forge-editor/composites read and build these values:

import { collectionItemsOf, collectionQueryOf, withCollectionItems } from '@skaile/forge-editor/composites'

collectionItemsOf(node)          // BlockAddress[]; entries that are no valid address are dropped
collectionQueryOf(node)          // DataQuery | null; null for no query, bad JSON or a query that names no Block type
withCollectionItems(node, items) // a copy of node's meta with `items` as its list; an empty list removes the key

collectionQueryOf checks only the shape (v: 1, a blocks source that names a Block type). Which fields exist is the job of validateQuery; the route answers 422 with the issues when the stored query is wrong.

Write the list through setBlockProperty (or setCollectionItems(editor, blockId, items), which does that): it writes the one key, so two collaborators who edit different keys of the same block keep both changes.

The view

Both bindings supply a node view for the collection (CompositeNodeViews.collection): the usual Typed block view with the entries below its content.

  • Every entry is a reference pill with the title of its Block (an entry whose Block is gone shows „Block gelöscht“). A click reveals the Block in the open Document, or opens it in its own Document through the host's openBlock.
  • Move up, move down and remove buttons appear beside an entry while the editor is editable.
  • „Aus Abfrage füllen“ shows when the collection has a stored query and backend.index.query exists. It runs the query, appends the hits that are not listed yet in the order of the answer, and tells the user how many it added (a collection never lists itself). A failed query leaves the list as it is and says why.

The view is features/composites/collection-model.ts (collectionViewOf, moveCollectionItem, removeCollectionItem, fillCollectionFromQuery), shared by CollectionView.vue and CollectionView.tsx. A binding that renders no live content leaves collection out of nodeViews; the collection then renders as any Typed block and its entries stay in meta.items.

Both bindings pass typedBlockViewOptions (@skaile/forge-editor/composites) to the renderer of typedBlock and collection, so a block whose Block type changes gets the view of its new type. A host that builds its own nodeViews does the same:

compositeNodes({
  typedBlock: VueNodeViewRenderer(CompositeView, typedBlockViewOptions),
  collection: VueNodeViewRenderer(CollectionView, typedBlockViewOptions),
  reference: VueNodeViewRenderer(ReferenceView),
})

Add a Block from anywhere

The block action collection-add („Zu Sammlung hinzufügen“, Ability composite.meta) is on every Typed block. Its menu lists the collections of the open Document by title (one that holds the Block already is disabled; the action is hidden when the Document has no other collection), and choosing one appends the Block's address.

Markdown export

The Markdown form of a collection lists its entries as reference lines, one chip per paragraph, in the stored order:

# Lesestoff

[@3f0c…]

[@doc-9#a41b…]

An entry in another Document carries the Document first ([@<documentId>#<blockId>]), the same form as a reference chip. documentCompositeMarkdown, headingsMarkdown and the markdown render target all go through compositeChromeBlocks, which appends collectionItemBlocks(schema, node) for a collection. The meta line leaves out hidden properties (the stored query) and reference-list properties, so neither shows as [object Object].

Copyright © 2026