x-openapi-flow 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,455 +0,0 @@
1
- window.XOpenApiFlowPlugin = function () {
2
- const h = React.createElement;
3
-
4
- function toPlain(value) {
5
- if (!value) return value;
6
- return value.toJS ? value.toJS() : value;
7
- }
8
-
9
- function text(value) {
10
- if (value === null || value === undefined || value === "") return "-";
11
- if (Array.isArray(value)) return value.length ? value.join(", ") : "-";
12
- return String(value);
13
- }
14
-
15
- function transitionsList(currentState, transitions) {
16
- if (!Array.isArray(transitions) || transitions.length === 0) {
17
- return h("div", { style: { opacity: 0.85, fontStyle: "italic" } }, "No transitions (terminal state)");
18
- }
19
-
20
- return h(
21
- "ul",
22
- { style: { margin: "6px 0 0 18px", padding: 0 } },
23
- transitions.map((transition, index) =>
24
- h(
25
- "li",
26
- { key: `${currentState}-${index}`, style: { marginBottom: "4px", lineHeight: 1.45 } },
27
- h("strong", null, text(transition.trigger_type)),
28
- " → ",
29
- h("strong", null, text(transition.target_state)),
30
- transition.condition ? ` — ${text(transition.condition)}` : "",
31
- transition.next_operation_id ? ` (next: ${text(transition.next_operation_id)})` : ""
32
- )
33
- )
34
- );
35
- }
36
-
37
- function miniGraph(currentState, transitions) {
38
- if (!Array.isArray(transitions) || transitions.length === 0) {
39
- return [h("div", { key: "terminal", style: { fontFamily: "monospace" } }, `${text(currentState)} [terminal]`)];
40
- }
41
-
42
- return transitions.map((transition, index) =>
43
- h(
44
- "div",
45
- { key: `edge-${index}`, style: { fontFamily: "monospace", lineHeight: 1.45 } },
46
- `${text(currentState)} --> ${text(transition.target_state)} [${text(transition.trigger_type)}]`
47
- )
48
- );
49
- }
50
-
51
- return {
52
- wrapComponents: {
53
- OperationSummary: (Original) => (props) => {
54
- const operation = props.operation;
55
- const flow = operation && operation.get && operation.get("x-openapi-flow");
56
-
57
- if (!flow) {
58
- return h(Original, props);
59
- }
60
-
61
- const flowObject = toPlain(flow) || {};
62
- const currentState = flowObject.current_state;
63
- const transitions = Array.isArray(flowObject.transitions) ? flowObject.transitions : [];
64
- const graphImageUrl = flowObject.graph_image_url || window.XOpenApiFlowGraphImageUrl;
65
-
66
- const metadataGrid = h(
67
- "div",
68
- {
69
- style: {
70
- display: "grid",
71
- gridTemplateColumns: "140px 1fr",
72
- gap: "4px 10px",
73
- fontSize: "12px",
74
- marginTop: "6px",
75
- },
76
- },
77
- h("div", { style: { opacity: 0.85 } }, "version"),
78
- h("div", null, text(flowObject.version)),
79
- h("div", { style: { opacity: 0.85 } }, "id"),
80
- h("div", null, text(flowObject.id)),
81
- h("div", { style: { opacity: 0.85 } }, "current_state"),
82
- h("div", null, text(currentState))
83
- );
84
-
85
- const graphImageNode = graphImageUrl
86
- ? h(
87
- "div",
88
- { style: { marginTop: "10px" } },
89
- h("div", { style: { fontWeight: 700, marginBottom: "6px" } }, "Flow graph image"),
90
- h("img", {
91
- src: graphImageUrl,
92
- alt: "x-openapi-flow graph",
93
- style: {
94
- width: "100%",
95
- maxWidth: "560px",
96
- border: "1px solid rgba(255,255,255,0.3)",
97
- borderRadius: "6px",
98
- },
99
- })
100
- )
101
- : null;
102
-
103
- return h(
104
- "div",
105
- null,
106
- h(Original, props),
107
- h(
108
- "div",
109
- {
110
- style: {
111
- marginTop: "8px",
112
- padding: "10px",
113
- border: "1px solid rgba(255,255,255,0.28)",
114
- borderRadius: "8px",
115
- background: "rgba(0,0,0,0.12)",
116
- fontSize: "12px",
117
- },
118
- },
119
- h("div", { style: { fontWeight: 700 } }, "x-openapi-flow"),
120
- metadataGrid,
121
- h("div", { style: { marginTop: "10px", fontWeight: 700 } }, "Transitions"),
122
- transitionsList(currentState, transitions),
123
- h("div", { style: { marginTop: "10px", fontWeight: 700 } }, "Flow graph (operation-level)"),
124
- h(
125
- "div",
126
- {
127
- style: {
128
- marginTop: "6px",
129
- border: "1px dashed rgba(255,255,255,0.32)",
130
- borderRadius: "6px",
131
- padding: "8px",
132
- },
133
- },
134
- ...miniGraph(currentState, transitions)
135
- ),
136
- graphImageNode
137
- )
138
- );
139
- },
140
- },
141
- };
142
- };
143
-
144
- (function () {
145
- const styleId = 'x-openapi-flow-ui-style';
146
-
147
- function injectStyles() {
148
- if (document.getElementById(styleId)) return;
149
-
150
- const style = document.createElement('style');
151
- style.id = styleId;
152
- style.textContent = `
153
- .xof-card { border: 1px solid rgba(255,255,255,0.28); border-radius: 8px; padding: 10px; background: rgba(0,0,0,0.12); }
154
- .xof-title { font-weight: 700; margin-bottom: 8px; }
155
- .xof-meta { display: grid; grid-template-columns: 140px 1fr; gap: 4px 10px; font-size: 12px; margin-bottom: 10px; }
156
- .xof-meta-label { opacity: 0.85; }
157
- .xof-list { margin: 0; padding-left: 18px; }
158
- .xof-list li { margin: 4px 0; }
159
- .xof-graph { margin-top: 10px; padding: 8px; border: 1px dashed rgba(255,255,255,0.32); border-radius: 6px; }
160
- .xof-graph-title { font-size: 12px; font-weight: 700; margin-bottom: 6px; }
161
- .xof-edge { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace; font-size: 12px; line-height: 1.45; white-space: pre-wrap; }
162
- .xof-empty { opacity: 0.85; font-style: italic; }
163
- .xof-overview { margin: 10px 0 16px; }
164
- .xof-overview img { width: 100%; max-width: 760px; border: 1px solid rgba(255,255,255,0.3); border-radius: 6px; background: #fff; }
165
- .xof-overview-code { margin-top: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace; font-size: 11px; opacity: 0.9; white-space: pre-wrap; }
166
- `;
167
-
168
- document.head.appendChild(style);
169
- }
170
-
171
- function text(value) {
172
- if (value === null || value === undefined || value === '') return '-';
173
- if (Array.isArray(value)) return value.length ? value.join(', ') : '-';
174
- return String(value);
175
- }
176
-
177
- function renderTransitions(currentState, transitions) {
178
- if (!Array.isArray(transitions) || transitions.length === 0) {
179
- return '<div class="xof-empty">No transitions (terminal state)</div>';
180
- }
181
-
182
- return `<ul class="xof-list">${transitions
183
- .map((transition) => {
184
- const condition = transition.condition ? ` — ${text(transition.condition)}` : '';
185
- const nextOperation = transition.next_operation_id ? ` (next: ${text(transition.next_operation_id)})` : '';
186
- return `<li><strong>${text(transition.trigger_type)}</strong> → <strong>${text(transition.target_state)}</strong>${condition}${nextOperation}</li>`;
187
- })
188
- .join('')}</ul>`;
189
- }
190
-
191
- function renderGraph(currentState, transitions) {
192
- if (!Array.isArray(transitions) || transitions.length === 0) {
193
- return `<div class="xof-edge">${text(currentState)} [terminal]</div>`;
194
- }
195
-
196
- return transitions
197
- .map((transition) => `<div class="xof-edge">${text(currentState)} --> ${text(transition.target_state)} [${text(transition.trigger_type)}]</div>`)
198
- .join('');
199
- }
200
-
201
- function renderCard(flow) {
202
- const transitions = Array.isArray(flow.transitions) ? flow.transitions : [];
203
- return `
204
- <div class="xof-card">
205
- <div class="xof-title">x-openapi-flow</div>
206
- <div class="xof-meta">
207
- <div class="xof-meta-label">version</div><div>${text(flow.version)}</div>
208
- <div class="xof-meta-label">id</div><div>${text(flow.id)}</div>
209
- <div class="xof-meta-label">current_state</div><div>${text(flow.current_state)}</div>
210
- </div>
211
- <div><strong>Transitions</strong></div>
212
- ${renderTransitions(flow.current_state, transitions)}
213
- <div class="xof-graph">
214
- <div class="xof-graph-title">Flow graph (operation-level)</div>
215
- ${renderGraph(flow.current_state, transitions)}
216
- </div>
217
- </div>
218
- `;
219
- }
220
-
221
- function getSpecFromUi() {
222
- try {
223
- if (!window.ui || !window.ui.specSelectors || !window.ui.specSelectors.specJson) {
224
- return null;
225
- }
226
-
227
- const spec = window.ui.specSelectors.specJson();
228
- return spec && spec.toJS ? spec.toJS() : spec;
229
- } catch (_error) {
230
- return null;
231
- }
232
- }
233
-
234
- function extractFlowsFromSpec(spec) {
235
- const result = [];
236
- const paths = (spec && spec.paths) || {};
237
- const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
238
-
239
- Object.entries(paths).forEach(([pathKey, pathItem]) => {
240
- if (!pathItem || typeof pathItem !== 'object') return;
241
-
242
- methods.forEach((method) => {
243
- const operation = pathItem[method];
244
- if (!operation || typeof operation !== 'object') return;
245
-
246
- const flow = operation['x-openapi-flow'];
247
- if (!flow || typeof flow !== 'object' || !flow.current_state) return;
248
-
249
- result.push({
250
- operationId: operation.operationId || `${method}_${pathKey}`,
251
- flow,
252
- });
253
- });
254
- });
255
-
256
- return result;
257
- }
258
-
259
- function hasFlowData(spec) {
260
- return extractFlowsFromSpec(spec).length > 0;
261
- }
262
-
263
- function buildOverviewMermaid(flows) {
264
- const lines = ['stateDiagram-v2'];
265
- const states = new Set();
266
- const seen = new Set();
267
-
268
- flows.forEach(({ flow }) => {
269
- const current = flow.current_state;
270
- if (!current) return;
271
-
272
- states.add(current);
273
- const transitions = Array.isArray(flow.transitions) ? flow.transitions : [];
274
- transitions.forEach((transition) => {
275
- const target = transition.target_state;
276
- if (!target) return;
277
- states.add(target);
278
-
279
- const labelParts = [];
280
- if (transition.next_operation_id) {
281
- labelParts.push(`next:${text(transition.next_operation_id)}`);
282
- }
283
- if (Array.isArray(transition.prerequisite_operation_ids) && transition.prerequisite_operation_ids.length) {
284
- labelParts.push(`requires:${transition.prerequisite_operation_ids.join(',')}`);
285
- }
286
- const label = labelParts.join(' | ');
287
- const key = `${current}::${target}::${label}`;
288
- if (seen.has(key)) return;
289
- seen.add(key);
290
- lines.push(` ${current} --> ${target}${label ? `: ${label}` : ''}`);
291
- });
292
- });
293
-
294
- Array.from(states)
295
- .sort()
296
- .forEach((state) => {
297
- lines.splice(1, 0, ` state ${state}`);
298
- });
299
-
300
- return lines.join('\n');
301
- }
302
-
303
- let mermaidLoaderPromise = null;
304
- function ensureMermaid() {
305
- if (window.mermaid) {
306
- return Promise.resolve(window.mermaid);
307
- }
308
-
309
- if (mermaidLoaderPromise) {
310
- return mermaidLoaderPromise;
311
- }
312
-
313
- mermaidLoaderPromise = new Promise((resolve, reject) => {
314
- const script = document.createElement('script');
315
- script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js';
316
- script.async = true;
317
- script.onload = () => {
318
- if (window.mermaid) {
319
- window.mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' });
320
- resolve(window.mermaid);
321
- } else {
322
- reject(new Error('Mermaid library not available after load'));
323
- }
324
- };
325
- script.onerror = () => reject(new Error('Could not load Mermaid library'));
326
- document.head.appendChild(script);
327
- });
328
-
329
- return mermaidLoaderPromise;
330
- }
331
-
332
- function svgToDataUri(svg) {
333
- const encoded = window.btoa(unescape(encodeURIComponent(svg)));
334
- return `data:image/svg+xml;base64,${encoded}`;
335
- }
336
-
337
- let overviewRenderedHash = null;
338
- let overviewRenderInProgress = false;
339
- let overviewPendingHash = null;
340
- async function renderOverview() {
341
- const spec = getSpecFromUi();
342
- const flows = extractFlowsFromSpec(spec);
343
- if (!flows.length) return;
344
-
345
- const mermaid = buildOverviewMermaid(flows);
346
- const currentHash = `${flows.length}:${mermaid}`;
347
- if (overviewRenderedHash === currentHash) return;
348
- if (overviewRenderInProgress && overviewPendingHash === currentHash) return;
349
-
350
- const infoContainer = document.querySelector('.swagger-ui .information-container');
351
- if (!infoContainer) return;
352
-
353
- let holder = document.getElementById('xof-overview-holder');
354
- if (!holder) {
355
- holder = document.createElement('div');
356
- holder.id = 'xof-overview-holder';
357
- holder.className = 'xof-overview xof-card';
358
- infoContainer.parentNode.insertBefore(holder, infoContainer.nextSibling);
359
- }
360
-
361
- holder.innerHTML = '<div class="xof-title">x-openapi-flow — Flow Overview</div><div class="xof-empty">Rendering Mermaid graph...</div>';
362
- overviewRenderInProgress = true;
363
- overviewPendingHash = currentHash;
364
-
365
- try {
366
- const mermaidLib = await ensureMermaid();
367
- const renderId = `xof-overview-${Date.now()}`;
368
- const renderResult = await mermaidLib.render(renderId, mermaid);
369
- const svg = renderResult && renderResult.svg ? renderResult.svg : renderResult;
370
- const dataUri = svgToDataUri(svg);
371
-
372
- holder.innerHTML = `
373
- <div class="xof-title">x-openapi-flow — Flow Overview</div>
374
- <img src="${dataUri}" alt="x-openapi-flow overview graph" />
375
- <details style="margin-top:8px;">
376
- <summary style="cursor:pointer;">Mermaid source</summary>
377
- <div class="xof-overview-code">${mermaid.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</div>
378
- </details>
379
- `;
380
- } catch (_error) {
381
- holder.innerHTML = `
382
- <div class="xof-title">x-openapi-flow — Flow Overview</div>
383
- <div class="xof-empty">Could not render Mermaid image in this environment.</div>
384
- <div class="xof-overview-code">${mermaid.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</div>
385
- `;
386
- } finally {
387
- overviewRenderInProgress = false;
388
- }
389
-
390
- overviewRenderedHash = currentHash;
391
- }
392
-
393
- function findXOpenApiFlowValueCell(opblock) {
394
- const rows = opblock.querySelectorAll('tr');
395
- for (const row of rows) {
396
- const cells = row.querySelectorAll('td');
397
- if (cells.length < 2) continue;
398
- if (cells[0].innerText.trim() === 'x-openapi-flow') {
399
- return cells[1];
400
- }
401
- }
402
- return null;
403
- }
404
-
405
- function enhanceOperation(opblock) {
406
- const valueCell = findXOpenApiFlowValueCell(opblock);
407
- if (!valueCell || valueCell.dataset.xofEnhanced === '1') return;
408
-
409
- const raw = valueCell.innerText.trim();
410
- if (!raw) return;
411
-
412
- let flow;
413
- try {
414
- flow = JSON.parse(raw);
415
- } catch (_error) {
416
- return;
417
- }
418
-
419
- valueCell.innerHTML = renderCard(flow);
420
- valueCell.dataset.xofEnhanced = '1';
421
- }
422
-
423
- function enhanceAll() {
424
- const spec = getSpecFromUi();
425
- if (!hasFlowData(spec)) {
426
- return;
427
- }
428
-
429
- injectStyles();
430
- const opblocks = document.querySelectorAll('.opblock');
431
- opblocks.forEach((opblock) => enhanceOperation(opblock));
432
- renderOverview().catch(() => {
433
- // keep plugin resilient in environments where async rendering fails
434
- });
435
- }
436
-
437
- let enhanceScheduled = false;
438
- function scheduleEnhance() {
439
- if (enhanceScheduled) return;
440
- enhanceScheduled = true;
441
- window.requestAnimationFrame(() => {
442
- enhanceScheduled = false;
443
- enhanceAll();
444
- });
445
- }
446
-
447
- const observer = new MutationObserver(() => {
448
- scheduleEnhance();
449
- });
450
-
451
- window.addEventListener('load', () => {
452
- scheduleEnhance();
453
- observer.observe(document.body, { childList: true, subtree: true });
454
- });
455
- })();