UNPKG

@atlaskit/editor-plugin-synced-block

Version:

SyncedBlock plugin for @atlaskit/editor-core

112 lines (91 loc) 6.35 kB
# Synced Blocks Plugin — Developer Agent Guide > **For workflow guidance, debugging, and cross-package task guides, load the `synced-blocks` > skill:** > `get_skill(skill_name_or_path="platform/packages/editor/.agents/skills/synced-blocks/SKILL.md")` --- ## Quick Context **Synced Blocks** lets users create reusable content blocks (source) that can be referenced across Confluence pages and Jira issue descriptions. This package is the core editor plugin it registers ADF nodes, toolbar/menu integration, commands, and ProseMirror plugins. **Two ADF node types:** - `bodiedSyncBlock` **Source** sync block (contains the editable content) - `syncBlock` **Reference** sync block (renders content fetched from Block Service) --- ## Plugin Internals (`src/`) ``` src/ ├── index.ts # Re-exports plugin + type ├── syncedBlockPlugin.tsx # Top-level: registers nodes, commands, UI, pm-plugins ├── syncedBlockPluginType.ts # TypeScript interfaces for options, shared state, dependencies ├── editor-actions/ └── index.ts # flushBodiedSyncBlocks, flushSyncBlocks, discardUnpublishedSyncBlocks (EDITOR-6473) ├── editor-commands/ └── index.ts # createSyncedBlock, copySyncedBlockReferenceToClipboardEditorCommand, copySyncedBlockReferenceToClipboard, editSyncedBlockSource, removeSyncedBlock, removeSyncedBlockAtPos, unsync ├── nodeviews/ ├── syncedBlock.tsx # NodeView for reference (syncBlock) — read-only, fetches from BE ├── lazySyncedBlock.tsx # Lazy-loaded wrapper for syncedBlock (EDITOR-6928) └── bodiedSyncedBlock.tsx # NodeView for source (bodiedSyncBlock) — nested editor with content ├── pm-plugins/ ├── main.ts # Core state machine: lifecycle, creation, deletion, cache, status decoration apply path ├── menu-and-toolbar-experiences.ts # Experience tracking for menu/toolbar interactions └── utils/ ├── track-sync-blocks.ts # Tracks mutations, updates shared state ├── handle-bodied-sync-block-creation.ts # Creation flow, local cache, retry logic ├── handle-bodied-sync-block-removal.ts # Deletion flow, BE synchronization ├── has-synced-blocks.ts # O(childCount) presence check (EDITOR-6928 lazy init) ├── transaction-inserts-synced-block.ts # Detect tr inserts a synced block (lazy init) ├── selection-decorations.ts # Selection decoration helpers ├── ignore-dom-event.ts # DOM event guard └── utils.ts # Misc shared helpers ├── ui/ # (grep the dir for the full current list — it grows often) ├── toolbar-components.tsx # Primary toolbar button ("Create Synced Block") ├── CreateSyncedBlockButton.tsx / CreateSyncedBlockDropdownItem.tsx # Toolbar/menu entry points ├── floating-toolbar.tsx # Node-level actions: delete, unsync, copy link, view locations ├── block-menu-components.tsx # Block menu entry ├── quick-insert.tsx # Slash command / quick insert config ├── SyncedLocationDropdown.tsx # "View synced locations" dropdown ├── DeleteConfirmationModal.tsx # Deletion confirmation dialog ├── SyncBlockRefresher.tsx # Periodic data refresh from backend ├── SyncBlockLabel.tsx # Source/reference label chrome ├── SyncBlockRendererWrapper.tsx # Node-view wrapper ├── SyncBlockSSRReactContextsProvider.tsx # Supplies React contexts during SSR └── Flag.tsx # Error/info flags (offline, copy notifications) └── types/ └── index.ts # FLAG_ID, SyncedBlockSharedState, BodiedSyncBlockDeletionStatus ``` ### Editor Actions This package exposes top-level **editor actions** (in `editor-actions/index.ts`) that products call from outside the plugin lifecycle: - `flushBodiedSyncBlocks(store)` flush all dirty source blocks - `flushSyncBlocks(store)` flush reference manager (e.g. on save) - `discardUnpublishedSyncBlocks(store)` delete unpublished blocks on cancel (added in EDITOR-6473; used by Confluence's editor cancel flow) ### Lazy Init & Perf (EDITOR-6928 / EDITOR-6930) `main.ts`: - Skips creating synced-block plugin state and node-views for documents with no synced blocks (`hasSyncedBlocks(doc)`). - Computes `statusDecorationSet` inside `apply()` and stores it on plugin state, then exposes it via an O(1) `decorations` prop instead of an O(n) `doc.descendants()` walk on every transaction. - Uses `sourceSyncBlockStoreManager.hasPendingCreations()` for an O(1) pending-creation early return in `buildStatusDecorations()`. ### Key Code Patterns **Creating a sync block** (flow through the code): 1. User triggers via toolbar/block menu/slash command `ui/toolbar-components.tsx` or `ui/block-menu-components.tsx` 2. Calls `editor-commands/createSyncedBlock` inserts `bodiedSyncBlock` node into document (marked as **pending creation** it is not persisted yet) 3. `pm-plugins/main.ts` detects new node `handle-bodied-sync-block-creation.ts` updates `sourceManager` state. Persistence happens later when the product layer calls `flush()` (via `flushBodiedSyncBlocks`), which creates the block in the Block Service and then reconciles identifiers via `commitPendingCreation()` 4. `menu-and-toolbar-experiences.ts` fires the experience event **Reference rendering** (flow through the code): 1. `nodeviews/syncedBlock.tsx` (or `lazySyncedBlock.tsx`) mounts for each `syncBlock` node 2. Calls `referenceManager.fetchSyncBlocksData(nodes)` batched/deduped Block Service fetch 3. Renders content via nested renderer from `editor-synced-block-renderer` 4. Subscribes via `referenceManager.subscribeToSyncBlock(...)` AGG WebSocket (Confluence) or Relay (Jira) for real-time updates