One or more profile cards with cropped avatars, biography, role, and social links.
npm install @shelamkoff/rector @shelamkoff/cropperimport { createEditor } from '@shelamkoff/rector'
import { Person } from '@shelamkoff/rector/plugins/person'
const editor = createEditor({
holder: document.querySelector('#editor'),
plugins: [new Person()],
})The registered block type is person. The class is also exported by the complete @shelamkoff/rector/plugins preset and can be loaded through @shelamkoff/rector/plugins/async.
{
"persons": [{
"avatar": "https://cdn.example/ada.jpg",
"name": "Ada",
"role": "Author",
"bio": "",
"links": [{ "type": "website", "url": "https://example.com" }]
}]
}| Field | Required | Meaning and constraints |
|---|---|---|
persons |
yes | Non-empty array of profile cards. The saved order is the display order. |
persons[].avatar |
yes | Empty string or canonical media URL. Cropping and uploading replace this value. |
persons[].name |
yes | Profile name string. A profile with both an empty name and empty avatar is treated as empty by the editor. |
persons[].role, persons[].bio |
yes | Role and biography strings; empty values are allowed. |
persons[].links |
yes | Array of social links; it may be empty. Every item needs a string type and a canonical link-policy url. |
All fields are present in normalized saved data even when their values are empty. Unknown application fields are not part of the contract and should be stored outside the block.
The active profile tab is view state, not document data: save() persists all profiles in their current order but does not persist which tab was open. Tab switching therefore remains available in read-only mode so a reader can inspect every profile; it does not create a history entry. Adding, removing, reordering, or editing profiles is disabled in read-only mode.
Callback avatar URLs must pass the shared media URL policy. The editor plugin requires @shelamkoff/cropper. The multi-card renderer requires @shelamkoff/carousel.
Every built-in block plugin accepts two style ownership options: injectStyles?: boolean defaults to true; set it to false when the host bundles that plugin's CSS. css?: string adds one host-provided stylesheet URL after the plugin default, or acts as the replacement URL when default injection is disabled.
uploadFile?: (file: File, context: { signal: AbortSignal }) => Promise<{ url: string }> uploads the cropped avatar as avatar.webp and must stop work when the supplied signal is aborted. Without it, the cropped avatar is embedded in persons[].avatar as a data URL, which increases document size. socialResolvers?: Array<{ test: RegExp | ((url: string) => boolean); type: string; icon?: string }> extends social-link icon resolution.
Multiple profiles; tab reordering; avatar crop/upload; social links; read-only profile navigation; deterministic dialog cleanup.
User actions exposed by the plugin enter the command pipeline through the supplied context.mutate() capability, so each completed action is one undo/redo step. The editor reference-counts the plugin's declared stylesheet URLs. Removing a block calls its cleanup hook; removing the editor calls destroy() for every remaining block and then releases shared plugin resources.
Do not remove the editor holder without first calling editor.destroy().
Use the matching renderer from @shelamkoff/rector/renderer/renderers/person. The VitePress guide documents configuration, commands and history, extension contracts, document migrations, styling, security, and lifecycle in a sequential form.