-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Expand file tree
/
Copy pathregistry.ts
More file actions
240 lines (219 loc) · 9.68 KB
/
Copy pathregistry.ts
File metadata and controls
240 lines (219 loc) · 9.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import { stripVersionSuffix } from '@sim/utils/string'
import type { BlockVisibilityState } from '@/lib/core/config/block-visibility'
import { overlayBlocks, resolveOverlayBlock } from '@/blocks/custom/overlay'
import { BLOCK_META_REGISTRY, BLOCK_REGISTRY } from '@/blocks/registry-maps'
import type {
BlockCategory,
BlockConfig,
BlockMeta,
BlockTemplate,
SuggestedSkill,
} from '@/blocks/types'
import { isHiddenUnder, overlayVisibility } from '@/blocks/visibility/context'
/**
* Normalize an external block type to its registry key form: dashes become
* underscores (some external sources use either form).
*/
function normalizeType(type: string): string {
return type.replace(/-/g, '_')
}
/** Get the block config for a single block type. Falls back to the custom-block overlay. */
export function getBlock(type: string): BlockConfig | undefined {
return BLOCK_REGISTRY[type] ?? BLOCK_REGISTRY[normalizeType(type)] ?? resolveOverlayBlock(type)
}
/** Whether any registered block is an unreleased `preview` block. Static — computed once. */
const HAS_PREVIEW_BLOCKS = Object.values(BLOCK_REGISTRY).some((block) => block.preview)
/**
* True when the visibility projection cannot change any block, so accessors can
* return raw arrays untouched: no `preview` blocks exist (they must be hidden
* even with a null state) and no kill-switch entries apply.
*/
function visibilityInert(vis: BlockVisibilityState | null): boolean {
if (HAS_PREVIEW_BLOCKS) return false
return vis === null || vis.disabled.size === 0
}
/**
* Effective hidden state for discovery surfaces: static `hideFromToolbar`
* (superseded versions, disabled custom blocks) plus the per-viewer visibility
* predicate ({@link isHiddenUnder}: unrevealed `preview` blocks — fail-closed
* even without a context — and kill-switched types).
*/
function effectiveHidden(block: BlockConfig, vis: BlockVisibilityState | null): boolean {
if (block.hideFromToolbar) return true
return isHiddenUnder(vis, block)
}
/**
* Project a block through the viewer's visibility: gated blocks become shallow
* clones with `hideFromToolbar: true` (CLONE-NOT-REMOVE — gated blocks must stay
* in `getAllBlocks()` output because `.find`-by-type consumers rely on it), and
* revealed-but-not-GA preview blocks get a display " (Preview)" name suffix.
* The `!block.hideFromToolbar` guard keeps already-hidden blocks (including
* disabled custom blocks) un-cloned and never suffixed.
*/
function projectBlock(block: BlockConfig, vis: BlockVisibilityState | null): BlockConfig {
if (effectiveHidden(block, vis) && !block.hideFromToolbar) {
return { ...block, hideFromToolbar: true }
}
if (block.preview && vis?.previewTagged.has(block.type)) {
return { ...block, name: `${block.name} (Preview)` }
}
return block
}
/**
* All block configs, including any in-scope custom blocks from the overlay,
* projected through the viewer's block visibility. Execution paths are
* unaffected: they resolve via the pure {@link getBlock}.
*/
export function getAllBlocks(): BlockConfig[] {
const all = [...Object.values(BLOCK_REGISTRY), ...overlayBlocks()]
const vis = overlayVisibility()
if (visibilityInert(vis)) return all
return all.map((block) => projectBlock(block, vis))
}
/** Find the block whose `tools.access` contains the given tool id. */
export function getBlockByToolName(toolName: string): BlockConfig | undefined {
return Object.values(BLOCK_REGISTRY).find((b) => b.tools?.access?.includes(toolName))
}
/**
* Resolve the canonical (highest-version) block for a base type. Handles
* versioned variants like `confluence_v2`: callers pass `confluence` and
* receive the latest implementation. Returns the registry key alongside the
* config so callers that need the canonical type identifier avoid re-deriving
* it.
*/
function resolveLatest(baseType: string): { type: string; config: BlockConfig } | undefined {
const normalized = normalizeType(baseType)
const versionPattern = new RegExp(`^${normalized}_v(\\d+)$`)
let latestKey: string | undefined
let latestVersion = -1
for (const key of Object.keys(BLOCK_REGISTRY)) {
const match = key.match(versionPattern)
if (!match) continue
const version = Number.parseInt(match[1]!, 10)
if (version > latestVersion) {
latestVersion = version
latestKey = key
}
}
if (latestKey) return { type: latestKey, config: BLOCK_REGISTRY[latestKey]! }
const config = BLOCK_REGISTRY[normalized]
return config ? { type: normalized, config } : undefined
}
/**
* Resolve the canonical (highest-version) block for a base type. Handles
* versioned variants like `confluence_v2`: callers pass `confluence` and
* receive the latest implementation.
*/
export function getLatestBlock(baseType: string): BlockConfig | undefined {
return resolveLatest(baseType)?.config
}
/** All blocks in a given category. */
export function getBlocksByCategory(category: BlockCategory): BlockConfig[] {
return Object.values(BLOCK_REGISTRY).filter((block) => block.category === category)
}
/**
* The canonical "latest-version, toolbar-visible" set of blocks for a
* category. This is the single source of truth shared by every surface that
* extracts blocks for presentation — the toolbar, the search/mention engine,
* and the integrations catalog. A block is included when its `category`
* matches and it is not effectively hidden: not `hideFromToolbar` (superseded
* versions), not an unrevealed `preview` block, and not kill-switched for the
* viewer. Visible blocks are projected so revealed preview blocks carry their
* " (Preview)" display suffix.
*/
export function getCanonicalBlocksByCategory(category: BlockCategory): BlockConfig[] {
const vis = overlayVisibility()
const blocks = [...Object.values(BLOCK_REGISTRY), ...overlayBlocks()].filter(
(block) => block.category === category && !effectiveHidden(block, vis)
)
return visibilityInert(vis) ? blocks : blocks.map((block) => projectBlock(block, vis))
}
/** All registered block type identifiers. */
export function getAllBlockTypes(): string[] {
return Object.keys(BLOCK_REGISTRY)
}
/** Whether the given string is a registered block type. Accepts hyphens as a dash-form alias. */
export function isValidBlockType(type: string): type is string {
return (
type in BLOCK_REGISTRY ||
normalizeType(type) in BLOCK_REGISTRY ||
Boolean(resolveOverlayBlock(type))
)
}
/**
* Get the presentation/catalog meta for a block type, resolving through the
* version suffix the same way {@link getTemplatesForBlock} does. Metas are
* keyed under the base type (e.g. `confluence`, not `confluence_v2`), so a
* versioned lookup falls back to the stripped base.
*/
export function getBlockMeta(type: string): BlockMeta | undefined {
const normalized = normalizeType(type)
return (
BLOCK_META_REGISTRY[type] ??
BLOCK_META_REGISTRY[normalized] ??
BLOCK_META_REGISTRY[stripVersionSuffix(normalized)]
)
}
/** All block metas keyed by block type. */
export function getAllBlockMeta(): Record<string, BlockMeta> {
return BLOCK_META_REGISTRY
}
/**
* A template scoped to a viewing block, enriched with `otherBlockTypes` —
* the integrations to render alongside the viewer in the icon cluster.
* Includes the template's owner block whenever the viewer is not the owner.
*/
export interface ScopedBlockTemplate extends BlockTemplate {
/** Block types (base form) to render alongside the viewing block in the icon cluster. */
otherBlockTypes: readonly string[]
/** Whether the viewing block owns this template, vs matching only via `alsoIntegrations`. */
isOwner: boolean
}
/**
* All templates whose owner block is `type` or which list `type` in their
* `alsoIntegrations`. Each returned template carries `otherBlockTypes` —
* the non-viewing integrations (owner + other alsoIntegrations) for icon
* cluster rendering.
*/
export function getTemplatesForBlock(type: string): ScopedBlockTemplate[] {
const base = stripVersionSuffix(type)
const collected: ScopedBlockTemplate[] = []
for (const [ownerType, meta] of Object.entries(BLOCK_META_REGISTRY)) {
if (!meta.templates) continue
const ownerBase = stripVersionSuffix(ownerType)
const isOwnerMatch = ownerBase === base
for (const template of meta.templates) {
const isAlsoMatch =
template.alsoIntegrations?.includes(base) || template.alsoIntegrations?.includes(type)
if (!isOwnerMatch && !isAlsoMatch) continue
const others: string[] = []
if (!isOwnerMatch) others.push(ownerBase)
for (const also of template.alsoIntegrations ?? []) {
const alsoBase = stripVersionSuffix(also)
if (alsoBase !== base && !others.includes(alsoBase)) others.push(alsoBase)
}
collected.push({ ...template, otherBlockTypes: others, isOwner: isOwnerMatch })
}
}
return collected
}
/**
* Popular, ready-to-add skills for a block type. Curated skills live on the
* base integration's meta, but a versioned catalog type (e.g. `notion_v2`) has
* its own meta entry that {@link getBlockMeta} resolves first and which may omit
* skills — so fall back to the stripped base meta. Returns an empty array when
* the integration has no curated skills.
*/
export function getSuggestedSkillsForBlock(type: string): readonly SuggestedSkill[] {
const direct = getBlockMeta(type)?.skills
if (direct && direct.length > 0) return direct
const base = stripVersionSuffix(normalizeType(type))
return BLOCK_META_REGISTRY[base]?.skills ?? []
}
/**
* Raw block registry map keyed by block type. Prefer the typed accessors
* (`getBlock`, `getAllBlocks`, `getCanonicalBlocksByCategory`); this alias is
* retained for callers that need the underlying record directly.
*/
export const registry: Record<string, BlockConfig> = BLOCK_REGISTRY
export type { BlockCategory }