@myrmidon/cadmus-thesaurus-store
v1.0.0
Published
Cadmus thesauri store components.
Readme
CadmusThesaurusStore
- 📦
@myrmidon/cadmus-thesaurus-store
This library contains components for displaying and editing Cadmus thesauri, built on top of @myrmidon/paged-data-browsers.
Thesauri
Thesauri represent taxonomies of any type you might be using in editing your content. For instance, in describing a writing you might have a set of ink colors to pick from. The models for the thesaurus and its entries are defined in an external package (@myrmidon/cadmus-core).
Essentially, a thesaurus is a list of terms, each having an ID and a human-readable value. Such list can be a simple, flat list, or a hierarchical list. In both cases, they are defined as objects having each a list of entries.
Each entry in a thesaurus object has:
- an arbitrarily assigned
id, which must be unique in the context of that object; - a text
valueto be displayed to the end user.
For instance, you might have a list of languages to pick from, like English, French, Italian, etc.: in this case, your thesaurus would have its own ID, e.g. languages, plus entries like eng=English, fre=French, ita=Italian, etc. Here I'm using ISO 639 for language codes, and using standards is always recommended when available; but IDs are totally arbitrary.
You can also have hierarchical thesauri, where entries are arranged in a hierarchy represented with dots in their IDs. For instance, say you want to represent this simple 2-levels hierarchy:
- language:
- language: phonology
- language: morphology
- language: syntax
You can use a dot in each entry to represent three children entries under the same node:
[
{ "id": "lang.pho", "value": "language: phonology" },
{ "id": "lang.mor", "value": "language: morphology" },
{ "id": "lang.syn", "value": "language: syntax" }
]By convention, for each dot in the ID there is a colon in the value, so that the full hierarchy is displayed in every entry.
Should you want to have a selectable entry also for the parent language node, you just have to add another one, like this:
[
{ "id": "lang.-", "value": "language" },
{ "id": "lang.pho", "value": "language: phonology" },
{ "id": "lang.mor", "value": "language: morphology" },
{ "id": "lang.syn", "value": "language: syntax" }
]Services
A couple of services are used by the components in this library:
StaticThesaurusPagedTreeStoreService
Implements PagedTreeStoreService<ThesaurusEntryNodeFilter>. It converts a flat array of ThesaurusEntry objects (all loaded at once — hence "static") into a paged tree, using the dot-separated IDs to infer hierarchy. For example, "animal.mammal.dog" is a child of "animal.mammal", which is a child of "animal".
Nodes are of type ThesaurusEntryPagedTreeNode (extends PagedTreeNode) and the filter is ThesaurusEntryNodeFilter (extends TreeNodeFilter with an optional label field).
An optional renderLabel function can be supplied to the constructor; a built-in helper renderLabelFromLastColon is provided and trims everything up to (and including) the last colon in the label, so "animal: mammal: dog" is rendered as just "dog".
EditableStaticThesaurusPagedTreeStoreService
Extends EditablePagedTreeStoreServiceBase<ThesaurusEntryNodeFilter> and is the editable counterpart of StaticThesaurusPagedTreeStoreService. It still loads all entries at once but tracks in-memory edits (add, update, delete) and can commit them all at once.
Additional method beyond the base interface:
getCurrentEntries(): returns the flatThesaurusEntry[]list with all pending (unsaved) changes applied — the correct data to use when persisting the thesaurus to a server.
Components
ThesaurusBrowserComponent
- 🔑
cadmus-thesaurus-browser
A readonly paged tree browser for thesaurus entries. This is a lower-level component used directly when you only need to browse and pick a single entry, without the higher-level tree wrapper.
- ▶️ inputs:
service(StaticThesaurusPagedTreeStoreService): the service instance to load nodes from. A default no-op service is used if not provided.
- 🔥 outputs:
nodePick: fired when a leaf node (an entry without children) is clicked.
The component also exposes a label search form (field + find/clear buttons) that calls store.findLabels() and store.removeHilites() on the underlying PagedTreeStore.
ThesaurusTreeComponent
- 🔑
cadmus-thesaurus-tree
The thesaurus tree component displays a set of hierarchical thesaurus entries in a tree, provided that each entry marks its hierarchy with dots, and allows you to pick any entry.
- ▶️ inputs:
entries(ThesaurusEntry[]): all the entries in the thesaurus. Use this for simple cases where the parent component properly triggers change detection when data changes.entries$(Observable<ThesaurusEntry[]>): alternative observable of entries. Use this when the parent uses OnPush or zoneless change detection and the regularentriesinput doesn't update properly.renderLabel((label: string) => string): optional function for rendering node labels. Defaults torenderLabelFromLastColon. The original label is always emitted viaentryChangeregardless of this function.
- 🔥 outputs:
entryChange: fired when a leaf node is picked. Only leaf nodes (entries without children) can be selected.
Internally, this component uses a StaticThesaurusPagedTreeStoreService instance and a ThesaurusBrowserComponent.
ThesaurusEntriesPickerComponent
- 🔑
cadmus-thesaurus-entries-picker
A compact, collapsible picker component for managing multiple entries selected from a hierarchical thesaurus. In its collapsed state it shows the picked entries as a chip list; when expanded it shows the thesaurus tree to pick from.
The component also allows users to enter custom entries beyond the thesaurus entries. Such entries are all marked by an initial $ prefix and do not belong to a thesaurus. This feature must be opted in via allowCustom.
- ▶️ inputs:
availableEntries(ThesaurusEntry[]): all the entries in the thesaurus. Required.hierarchicLabels(boolean): true to show the entries with labels shortened according to their hierarchy.autoSort(boolean): true to automatically sort the picked entries alphabetically (disables drag-and-drop). When false, entries can be manually reordered via drag-and-drop.allowCustom(boolean): true to allow custom values (not in the entries list).minEntries(number): the minimum number of picked entries.maxEntries(number): the maximum number of picked entries (0=unlimited).emptyMessage(string): the message to show when there are no picked entries.
- 🔁 two-way bindings (Angular signals model):
entries(ThesaurusEntry[]): the currently picked entries.expanded(boolean): true when the picker is expanded rather than collapsed.
EditableThesaurusBrowserComponent
- 🔑
cadmus-editable-thesaurus-browser
An editable paged tree browser for thesaurus entries. Editing happens in memory and when complete the changes can be committed to the underlying data store via EditableStaticThesaurusPagedTreeStoreService.
- ▶️ inputs:
service(EditableStaticThesaurusPagedTreeStoreService): the editable service instance used to load nodes. Required.hasRootFilter(boolean): true to show the tree root nodes filter.
- 🔥 outputs:
nodePick: fired when an entry node is clicked.changesStateChange(boolean): fired when the unsaved-changes state changes. Subscribers can use this to enable/disable save and discard buttons.
Editing operations available via toolbar buttons in the template:
- Add child: add a new child node under the selected node.
- Add sibling: add a new sibling node next to the selected node.
- Edit node: change the label of the selected node.
- Remove node: delete the selected node and all its descendants (with confirmation).
The component also exposes a label search form and expand/collapse-all buttons, identical to ThesaurusBrowserComponent.
ThesaurusPagedTreeFilterComponent
- 🔑
cadmus-thesaurus-paged-tree-filter
A filter component for thesaurus paged tree browsers. It can work both as a standalone component (emitting the filter via the filter model) and as a Material dialog (returning the filter when closed).
- 🔁 two-way binding (Angular signal model):
filter(ThesaurusEntryNodeFilter | null | undefined): the current filter value.
When used as a dialog (opened by MatDialog), it closes with the new filter or with null when reset.
ThesEntryEditorComponent
- 🔑
cadmus-thesaurus-entry-editor
A dialog component for editing a single thesaurus entry (ID and value). Used internally by EditableThesaurusBrowserComponent for add/edit operations.
Input data (EditedThesEntry):
id(string): the entry's current ID.value(string): the entry's current value (label).hierarchical(boolean): when true andparentIdis set, the ID field is validated to start with the parent ID prefix followed by a dot.parentId(string): the string ID of the parent entry, used to build the hierarchical prefix.idLocked(boolean): when true, the ID field is read-only (used when editing an existing entry).idExists((id: string) => boolean): a validation callback to prevent duplicate IDs.
