(el: HTMLElement | string, options: CreateCodeExampleOptions)
| 65 | * then passes it in along with the path and method to render. |
| 66 | */ |
| 67 | export const createCodeExample = (el: HTMLElement | string, options: CreateCodeExampleOptions) => { |
| 68 | const element = typeof el === 'string' ? document.querySelector(el) : el |
| 69 | if (!element) { |
| 70 | throw new Error(`Element not found: ${el}`) |
| 71 | } |
| 72 | |
| 73 | const eventBus = createWorkspaceEventBus() |
| 74 | const clientOptions = generateClientOptions() |
| 75 | |
| 76 | // Persist client changes back to the store. The block emits |
| 77 | // `workspace:update:selected-client` on its private bus when the user picks a |
| 78 | // client; without this subscription the choice would never reach the store |
| 79 | // and `x-scalar-default-client` (the source of truth read below) would stay |
| 80 | // stale, so the selection would not survive a re-render or be shared with |
| 81 | // other blocks reading from the same store. |
| 82 | const mutators = generateClientMutators(options.store) |
| 83 | const unsubscribeClient = eventBus.on('workspace:update:selected-client', (payload) => |
| 84 | mutators.workspace().workspace.updateSelectedClient(payload), |
| 85 | ) |
| 86 | |
| 87 | // Persist example changes back to the store as well, mirroring the client |
| 88 | // round-trip above. The block emits `workspace:update:selected-example` when |
| 89 | // the user picks an example; without this the choice would never reach |
| 90 | // `x-scalar-default-example` (read below) and would neither survive a |
| 91 | // re-render nor sync to other blocks reading from the same store. |
| 92 | const unsubscribeExample = eventBus.on('workspace:update:selected-example', (payload) => |
| 93 | mutators.workspace().workspace.updateSelectedExample(payload), |
| 94 | ) |
| 95 | |
| 96 | type OperationContext = { operation: OperationObject; server: ServerObject | null } |
| 97 | |
| 98 | /** |
| 99 | * Resolve the operation and its effective server from the active document. |
| 100 | * |
| 101 | * The server is resolved from the same document the operation came from |
| 102 | * (operation, then path item, then document level), so a snippet never pairs |
| 103 | * an operation with a server URL from a different spec. |
| 104 | */ |
| 105 | const resolveContext = (): OperationContext | undefined => { |
| 106 | // The active document may be an AsyncAPI document, which has no `paths`. |
| 107 | const activeDocument = options.store.workspace.activeDocument |
| 108 | if (!activeDocument || !('paths' in activeDocument)) { |
| 109 | return undefined |
| 110 | } |
| 111 | const pathItem = getResolvedRef(activeDocument.paths?.[options.path]) |
| 112 | const operation = getResolvedRef(pathItem?.[options.method]) |
| 113 | if (!operation) { |
| 114 | return undefined |
| 115 | } |
| 116 | const server = getFirstServer(operation.servers ?? null, pathItem?.servers ?? null, activeDocument.servers ?? null) |
| 117 | return { operation, server } |
| 118 | } |
| 119 | |
| 120 | // Fail loudly if the path and method do not point at an operation in the store, |
| 121 | // mirroring the "element not found" guard above. |
| 122 | if (!resolveContext()) { |
| 123 | throw new Error(`Operation not found: ${options.method.toUpperCase()} ${options.path}`) |
| 124 | } |
no test coverage detected