@zolomedia/bifrost-client 1.7.74
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.
- package/L1_Foundation/L1_Foundation.js +13 -0
- package/L1_Foundation/bootstrap/bootstrap.js +11 -0
- package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
- package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
- package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
- package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
- package/L1_Foundation/bootstrap/module_registry.js +102 -0
- package/L1_Foundation/bootstrap/prism_loader.js +164 -0
- package/L1_Foundation/config/client_config.js +110 -0
- package/L1_Foundation/config/config.js +7 -0
- package/L1_Foundation/connection/connection.js +8 -0
- package/L1_Foundation/connection/websocket_connection.js +122 -0
- package/L1_Foundation/constants/bifrost_constants.js +284 -0
- package/L1_Foundation/constants/constants.js +7 -0
- package/L1_Foundation/logger/logger.js +10 -0
- package/L2_Handling/L2_Handling.js +15 -0
- package/L2_Handling/cache/cache.js +22 -0
- package/L2_Handling/cache/cache_constants.js +69 -0
- package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
- package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
- package/L2_Handling/cache/orchestration/orchestration.js +12 -0
- package/L2_Handling/cache/storage/session_manager.js +289 -0
- package/L2_Handling/cache/storage/storage.js +10 -0
- package/L2_Handling/cache/storage/storage_manager.js +590 -0
- package/L2_Handling/display/composite/composite.js +13 -0
- package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
- package/L2_Handling/display/composite/swiper_renderer.js +564 -0
- package/L2_Handling/display/composite/terminal_renderer.js +922 -0
- package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
- package/L2_Handling/display/display.js +30 -0
- package/L2_Handling/display/feedback/feedback.js +11 -0
- package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
- package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
- package/L2_Handling/display/inputs/button_renderer.js +634 -0
- package/L2_Handling/display/inputs/form_renderer.js +583 -0
- package/L2_Handling/display/inputs/input_renderer.js +658 -0
- package/L2_Handling/display/inputs/inputs.js +12 -0
- package/L2_Handling/display/navigation/menu_renderer.js +206 -0
- package/L2_Handling/display/navigation/navigation.js +11 -0
- package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
- package/L2_Handling/display/orchestration/orchestration.js +11 -0
- package/L2_Handling/display/orchestration/renderer.js +430 -0
- package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
- package/L2_Handling/display/outputs/alert_renderer.js +161 -0
- package/L2_Handling/display/outputs/audio_renderer.js +94 -0
- package/L2_Handling/display/outputs/card_renderer.js +229 -0
- package/L2_Handling/display/outputs/code_renderer.js +66 -0
- package/L2_Handling/display/outputs/dl_renderer.js +131 -0
- package/L2_Handling/display/outputs/header_renderer.js +162 -0
- package/L2_Handling/display/outputs/icon_renderer.js +107 -0
- package/L2_Handling/display/outputs/image_renderer.js +145 -0
- package/L2_Handling/display/outputs/list_renderer.js +190 -0
- package/L2_Handling/display/outputs/outputs.js +19 -0
- package/L2_Handling/display/outputs/table_renderer.js +765 -0
- package/L2_Handling/display/outputs/text_renderer.js +818 -0
- package/L2_Handling/display/outputs/typography_renderer.js +293 -0
- package/L2_Handling/display/outputs/video_renderer.js +116 -0
- package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
- package/L2_Handling/display/primitives/form_primitives.js +526 -0
- package/L2_Handling/display/primitives/generic_containers.js +109 -0
- package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
- package/L2_Handling/display/primitives/link_primitives.js +552 -0
- package/L2_Handling/display/primitives/lists_primitives.js +262 -0
- package/L2_Handling/display/primitives/media_primitives.js +383 -0
- package/L2_Handling/display/primitives/primitives.js +19 -0
- package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
- package/L2_Handling/display/primitives/table_primitives.js +528 -0
- package/L2_Handling/display/primitives/typography_primitives.js +175 -0
- package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
- package/L2_Handling/display/specialized/specialized.js +10 -0
- package/L2_Handling/hooks/hooks.js +9 -0
- package/L2_Handling/hooks/menu_integration.js +57 -0
- package/L2_Handling/hooks/widget_hook_manager.js +292 -0
- package/L2_Handling/message/message.js +8 -0
- package/L2_Handling/message/message_handler.js +701 -0
- package/L2_Handling/navigation/navigation.js +8 -0
- package/L2_Handling/navigation/navigation_manager.js +403 -0
- package/L2_Handling/zhooks/features/cache_live.js +287 -0
- package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
- package/L2_Handling/zhooks/zhooks_manager.js +65 -0
- package/L2_Handling/zvaf/zvaf.js +8 -0
- package/L2_Handling/zvaf/zvaf_manager.js +334 -0
- package/L3_Abstraction/L3_Abstraction.js +12 -0
- package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
- package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
- package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
- package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
- package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
- package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
- package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
- package/L3_Abstraction/renderer/renderer.js +1 -0
- package/L3_Abstraction/session/session.js +1 -0
- package/L4_Orchestration/L4_Orchestration.js +11 -0
- package/L4_Orchestration/client/client.js +1 -0
- package/L4_Orchestration/facade/facade.js +9 -0
- package/L4_Orchestration/facade/manager_registry.js +118 -0
- package/L4_Orchestration/facade/renderer_registry.js +274 -0
- package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
- package/L4_Orchestration/lifecycle/initializer.js +135 -0
- package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
- package/L4_Orchestration/rendering/facade.js +94 -0
- package/L4_Orchestration/rendering/rendering.js +7 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/bifrost_client.js +204 -0
- package/bifrost_core.js +1686 -0
- package/docs/ARCHITECTURE.md +111 -0
- package/docs/PROTOCOL.md +106 -0
- package/docs/RENDERERS.md +101 -0
- package/docs/SECURITY.md +92 -0
- package/package.json +24 -0
- package/syntax/prism-zconfig.js +41 -0
- package/syntax/prism-zenv.js +69 -0
- package/syntax/prism-zolo-theme.css +288 -0
- package/syntax/prism-zolo.js +380 -0
- package/syntax/prism-zschema.js +38 -0
- package/syntax/prism-zspark.js +25 -0
- package/syntax/prism-zui.js +68 -0
- package/zSys/accessibility/accessibility.js +10 -0
- package/zSys/accessibility/emoji_accessibility.js +173 -0
- package/zSys/dom/block_utils.js +122 -0
- package/zSys/dom/container_utils.js +370 -0
- package/zSys/dom/dom.js +13 -0
- package/zSys/dom/dom_utils.js +328 -0
- package/zSys/dom/encoding_utils.js +117 -0
- package/zSys/dom/style_utils.js +71 -0
- package/zSys/errors/error_display.js +299 -0
- package/zSys/errors/errors.js +10 -0
- package/zSys/theme/color_utils.js +274 -0
- package/zSys/theme/dark_mode_utils.js +272 -0
- package/zSys/theme/size_utils.js +256 -0
- package/zSys/theme/spacing_utils.js +405 -0
- package/zSys/theme/theme.js +14 -0
- package/zSys/theme/zbase.css +1735 -0
- package/zSys/theme/zbase_inject.js +161 -0
- package/zSys/theme/ztheme_utils.js +305 -0
- package/zSys/validation/error_boundary.js +201 -0
- package/zSys/validation/validation.js +11 -0
- package/zSys/validation/validation_utils.js +238 -0
- package/zSys/zSys.js +14 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Alert Renderer - Signal/Feedback Messages
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* Renders alert/signal events from zCLI backend (zSignals subsystem).
|
|
7
|
+
* Provides visual feedback for success, info, warning, and error states.
|
|
8
|
+
*
|
|
9
|
+
* @module rendering/alert_renderer
|
|
10
|
+
* @layer 3
|
|
11
|
+
* @pattern Strategy (single event family)
|
|
12
|
+
*
|
|
13
|
+
* Philosophy:
|
|
14
|
+
* - "Terminal first" - alerts are visual feedback primitives
|
|
15
|
+
* - Pure rendering (no auto-dismiss, no complex animations)
|
|
16
|
+
* - Semantic colors (success=green, info=blue, warning=orange, error=red)
|
|
17
|
+
* - Uses Layer 2 utilities exclusively (no inline logic)
|
|
18
|
+
*
|
|
19
|
+
* Dependencies:
|
|
20
|
+
* - Layer 2: dom_utils.js, ztheme_utils.js
|
|
21
|
+
*
|
|
22
|
+
* Exports:
|
|
23
|
+
* - AlertRenderer: Class for rendering alert/signal events
|
|
24
|
+
*
|
|
25
|
+
* Example:
|
|
26
|
+
* ```javascript
|
|
27
|
+
* import { AlertRenderer } from './alert_renderer.js';
|
|
28
|
+
*
|
|
29
|
+
* const renderer = new AlertRenderer(logger);
|
|
30
|
+
* renderer.render({
|
|
31
|
+
* content: 'Operation successful!',
|
|
32
|
+
* eventType: 'success'
|
|
33
|
+
* }, 'zVaF');
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────
|
|
38
|
+
// Imports
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
// Layer 2: Utilities
|
|
42
|
+
import { createElement, setAttributes } from '../../../zSys/dom/dom_utils.js';
|
|
43
|
+
import { getAlertColorClass } from '../../../zSys/theme/ztheme_utils.js';
|
|
44
|
+
import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
|
|
45
|
+
|
|
46
|
+
//
|
|
47
|
+
// Alert Renderer Class
|
|
48
|
+
//
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* AlertRenderer - Renders signal/alert events
|
|
52
|
+
*
|
|
53
|
+
* Handles zCLI signal events (error, warning, success, info) which
|
|
54
|
+
* provide visual feedback to users. Maps to zTheme's zSignal components.
|
|
55
|
+
*
|
|
56
|
+
* Event Mapping:
|
|
57
|
+
* - error → zSignal-error (red)
|
|
58
|
+
* - warning → zSignal-warning (orange)
|
|
59
|
+
* - success → zSignal-success (green)
|
|
60
|
+
* - info → zSignal-info (blue)
|
|
61
|
+
*/
|
|
62
|
+
export class AlertRenderer {
|
|
63
|
+
/**
|
|
64
|
+
* Create an AlertRenderer instance
|
|
65
|
+
* @param {Object} logger - Logger instance for debugging
|
|
66
|
+
*/
|
|
67
|
+
constructor(logger) {
|
|
68
|
+
this.logger = logger || console;
|
|
69
|
+
this.logger.debug('[AlertRenderer] Initialized');
|
|
70
|
+
|
|
71
|
+
// Wrap render method with error boundary
|
|
72
|
+
const originalRender = this.render.bind(this);
|
|
73
|
+
this.render = withErrorBoundary(originalRender, {
|
|
74
|
+
component: 'AlertRenderer',
|
|
75
|
+
logger: this.logger
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Render an alert/signal event
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} data - Alert event data
|
|
83
|
+
* @param {string} data.content - Alert message text
|
|
84
|
+
* @param {string} data.eventType - Event type (error, warning, success, info)
|
|
85
|
+
* @param {number} [data.indent=0] - Indentation level (0 = no indent)
|
|
86
|
+
* @param {string} [data.class] - Custom CSS class (optional)
|
|
87
|
+
* @param {string} zone - Target DOM element ID
|
|
88
|
+
* @returns {HTMLElement|null} Created alert element or null if failed
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* renderer.render({ content: 'Success!', eventType: 'success' }, 'zVaF');
|
|
92
|
+
* renderer.render({ content: 'Warning!', eventType: 'warning', indent: 1 }, 'zVaF');
|
|
93
|
+
* renderer.render({ content: 'Error!', eventType: 'error' }, 'zVaF');
|
|
94
|
+
*/
|
|
95
|
+
render(data, zone) {
|
|
96
|
+
const { content, eventType, indent = 0, class: customClass } = data;
|
|
97
|
+
|
|
98
|
+
// Validate required parameters
|
|
99
|
+
if (!content) {
|
|
100
|
+
this.logger.error('[AlertRenderer] [ERROR] Missing required parameter: content');
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!eventType) {
|
|
105
|
+
this.logger.error('[AlertRenderer] [ERROR] Missing required parameter: eventType');
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Get target container
|
|
110
|
+
const container = document.getElementById(zone);
|
|
111
|
+
if (!container) {
|
|
112
|
+
this.logger.error(`[AlertRenderer] [ERROR] Zone not found: ${zone}`);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Build CSS classes array
|
|
117
|
+
const classes = ['zSignal'];
|
|
118
|
+
|
|
119
|
+
// Add alert color class (uses Layer 2 utility)
|
|
120
|
+
const alertClass = getAlertColorClass(eventType);
|
|
121
|
+
if (alertClass) {
|
|
122
|
+
classes.push(alertClass);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add custom class if provided (from YAML)
|
|
126
|
+
if (customClass) {
|
|
127
|
+
classes.push(customClass);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Create alert element (using Layer 2 utility)
|
|
131
|
+
const alert = createElement('div', classes);
|
|
132
|
+
alert.textContent = content; // Use textContent for XSS safety
|
|
133
|
+
|
|
134
|
+
// Apply attributes
|
|
135
|
+
const attributes = {
|
|
136
|
+
role: 'alert' // ARIA role for accessibility
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Apply indent as inline style (zTheme doesn't have indent utilities)
|
|
140
|
+
// Each indent level = 1rem left margin
|
|
141
|
+
if (indent > 0) {
|
|
142
|
+
attributes.style = `margin-left: ${indent}rem;`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setAttributes(alert, attributes);
|
|
146
|
+
|
|
147
|
+
// Append to container
|
|
148
|
+
container.appendChild(alert);
|
|
149
|
+
|
|
150
|
+
// Log success
|
|
151
|
+
this.logger.log(`[AlertRenderer] Rendered ${eventType} alert (${content.length} chars, indent: ${indent})`);
|
|
152
|
+
|
|
153
|
+
return alert;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//
|
|
158
|
+
// Default Export
|
|
159
|
+
//
|
|
160
|
+
export default AlertRenderer;
|
|
161
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Audio Renderer - Media Display Events
|
|
4
|
+
*
|
|
5
|
+
* Terminal-First Design:
|
|
6
|
+
* - Backend sends src, alt_text, caption (works with URLs, served paths)
|
|
7
|
+
* - Terminal displays metadata + button to open (zOpen)
|
|
8
|
+
* - Bifrost renders a native <audio controls> element with zTheme styling
|
|
9
|
+
*
|
|
10
|
+
* Mirrors ImageRenderer / VideoRenderer: primitives-first, _zClass / _id
|
|
11
|
+
* passthrough, optional <figure>/<figcaption> when a caption is present.
|
|
12
|
+
*
|
|
13
|
+
* @module rendering/audio_renderer
|
|
14
|
+
* @layer 3
|
|
15
|
+
* @pattern Primitives-First
|
|
16
|
+
*
|
|
17
|
+
* Exports:
|
|
18
|
+
* - AudioRenderer: Class for rendering audio events
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
|
|
22
|
+
|
|
23
|
+
export class AudioRenderer {
|
|
24
|
+
constructor(logger) {
|
|
25
|
+
this.logger = logger;
|
|
26
|
+
|
|
27
|
+
const originalRender = this.render.bind(this);
|
|
28
|
+
this.render = withErrorBoundary(originalRender, {
|
|
29
|
+
component: 'AudioRenderer',
|
|
30
|
+
logger: this.logger
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Render an <audio> element from event primitives.
|
|
36
|
+
*
|
|
37
|
+
* @param {Object} eventData - Event data from backend
|
|
38
|
+
* @param {string} eventData.src - Audio source (URL or served path)
|
|
39
|
+
* @param {string} [eventData.alt_text] - Accessibility label (→ aria-label)
|
|
40
|
+
* @param {string} [eventData.caption] - Optional caption (→ <figcaption>)
|
|
41
|
+
* @param {boolean} [eventData.loop] - Loop playback
|
|
42
|
+
* @param {boolean} [eventData.muted] - Start muted
|
|
43
|
+
* @param {string} [eventData._zClass] - Custom classes for styling
|
|
44
|
+
* @param {string} [eventData._id] - Custom DOM id for targeting
|
|
45
|
+
* @returns {HTMLElement} Audio element (or <figure> when captioned)
|
|
46
|
+
*/
|
|
47
|
+
render(eventData) {
|
|
48
|
+
const { src, alt_text, caption, _zClass, _id, loop, muted } = eventData;
|
|
49
|
+
|
|
50
|
+
if (!src) {
|
|
51
|
+
this.logger.error('[AudioRenderer] Missing src parameter');
|
|
52
|
+
return this._createErrorElement();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.logger.debug(`[AudioRenderer] Rendering audio: ${src}`);
|
|
56
|
+
|
|
57
|
+
const audio = document.createElement('audio');
|
|
58
|
+
audio.src = src;
|
|
59
|
+
audio.controls = true;
|
|
60
|
+
audio.preload = 'metadata';
|
|
61
|
+
audio.style.maxWidth = '100%';
|
|
62
|
+
|
|
63
|
+
if (_zClass) audio.className = _zClass;
|
|
64
|
+
if (_id) audio.setAttribute('id', _id);
|
|
65
|
+
if (alt_text) audio.setAttribute('aria-label', alt_text);
|
|
66
|
+
if (loop) audio.loop = true;
|
|
67
|
+
if (muted) audio.muted = true;
|
|
68
|
+
|
|
69
|
+
if (caption) {
|
|
70
|
+
const figure = document.createElement('figure');
|
|
71
|
+
figure.appendChild(audio);
|
|
72
|
+
const figcaption = document.createElement('figcaption');
|
|
73
|
+
figcaption.textContent = caption;
|
|
74
|
+
figcaption.className = 'zText-muted zText-center zmt-2';
|
|
75
|
+
figure.appendChild(figcaption);
|
|
76
|
+
// _zClass/_zStyle live on the inner <audio>; skip the central pass on the wrapper.
|
|
77
|
+
figure.__zMetaScoped = true;
|
|
78
|
+
this.logger.debug('[AudioRenderer] Audio with caption rendered');
|
|
79
|
+
return figure;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.logger.debug('[AudioRenderer] Audio rendered');
|
|
83
|
+
return audio;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_createErrorElement() {
|
|
87
|
+
const error = document.createElement('div');
|
|
88
|
+
error.className = 'zAlert zAlert-danger';
|
|
89
|
+
error.textContent = '[WARN] Audio source missing';
|
|
90
|
+
return error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default AudioRenderer;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Card Renderer - zTheme Card Components
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* Renders card/panel components aligned with zTheme card system:
|
|
7
|
+
* - zCard (basic cards with proper body wrapper)
|
|
8
|
+
* - zCard-header/footer (optional sections)
|
|
9
|
+
* - zCard-title/subtitle/text (typography)
|
|
10
|
+
* - Color variants (zCard-primary, zCard-success, etc.)
|
|
11
|
+
*
|
|
12
|
+
* REFACTORED: Uses Layer 0 primitives + Layer 2 utilities
|
|
13
|
+
*
|
|
14
|
+
* @module rendering/card_renderer
|
|
15
|
+
* @layer 3
|
|
16
|
+
* @pattern Strategy (card components)
|
|
17
|
+
*
|
|
18
|
+
* @see https://github.com/ZoloAi/zTheme/blob/main/src/css/zCards.css
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────
|
|
22
|
+
// Imports
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
// Layer 0: Primitives
|
|
26
|
+
import { createDiv } from '../primitives/generic_containers.js';
|
|
27
|
+
import { createHeading, createParagraph } from '../primitives/typography_primitives.js';
|
|
28
|
+
|
|
29
|
+
export default class CardRenderer {
|
|
30
|
+
/**
|
|
31
|
+
* Create a CardRenderer instance
|
|
32
|
+
* @param {Object} logger - Logger instance for debugging
|
|
33
|
+
*/
|
|
34
|
+
constructor(logger) {
|
|
35
|
+
this.logger = logger || console;
|
|
36
|
+
this.logger.debug('[CardRenderer] Initialized');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Enhance a container with proper zCard structure
|
|
41
|
+
*
|
|
42
|
+
* Transforms:
|
|
43
|
+
* <div class="zCard">
|
|
44
|
+
* <h2>Title</h2>
|
|
45
|
+
* <p>Content</p>
|
|
46
|
+
* </div>
|
|
47
|
+
*
|
|
48
|
+
* Into:
|
|
49
|
+
* <div class="zCard">
|
|
50
|
+
* <div class="zCard-body">
|
|
51
|
+
* <h2 class="zCard-title">Title</h2>
|
|
52
|
+
* <p class="zCard-text">Content</p>
|
|
53
|
+
* </div>
|
|
54
|
+
* </div>
|
|
55
|
+
*
|
|
56
|
+
* @param {HTMLElement} container - Container element with zCard class
|
|
57
|
+
* @returns {HTMLElement} Enhanced card element
|
|
58
|
+
*/
|
|
59
|
+
enhanceCard(container) {
|
|
60
|
+
if (!container || !container.classList.contains('zCard')) {
|
|
61
|
+
return container;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check if zCard-body already exists
|
|
65
|
+
const existingBody = container.querySelector(':scope > .zCard-body');
|
|
66
|
+
if (existingBody) {
|
|
67
|
+
this.logger.log('[CardRenderer] Card already has zCard-body, skipping enhancement');
|
|
68
|
+
return container;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Create zCard-body wrapper (using primitive)
|
|
72
|
+
const cardBody = createDiv({ class: 'zCard-body' });
|
|
73
|
+
|
|
74
|
+
// Move all direct children into the card body
|
|
75
|
+
while (container.firstChild) {
|
|
76
|
+
const child = container.firstChild;
|
|
77
|
+
|
|
78
|
+
// Apply zCard typography classes based on element type
|
|
79
|
+
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
80
|
+
this._applyCardTypography(child);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
cardBody.appendChild(child);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Append the card body back to the container
|
|
87
|
+
container.appendChild(cardBody);
|
|
88
|
+
|
|
89
|
+
this.logger.log('[CardRenderer] Enhanced card with zCard-body wrapper');
|
|
90
|
+
return container;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Apply zCard typography classes to elements
|
|
95
|
+
* @private
|
|
96
|
+
* @param {HTMLElement} element - Element to enhance
|
|
97
|
+
*/
|
|
98
|
+
_applyCardTypography(element) {
|
|
99
|
+
const tagName = element.tagName.toLowerCase();
|
|
100
|
+
|
|
101
|
+
// Apply zCard-title to first heading (h1-h6)
|
|
102
|
+
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
|
103
|
+
// Check if this is the first heading in the card
|
|
104
|
+
const parentCard = element.closest('.zCard');
|
|
105
|
+
if (parentCard) {
|
|
106
|
+
const existingTitle = parentCard.querySelector('.zCard-title');
|
|
107
|
+
if (!existingTitle) {
|
|
108
|
+
element.classList.add('zCard-title');
|
|
109
|
+
this.logger.log(`[CardRenderer] Applied zCard-title to ${tagName}`);
|
|
110
|
+
} else {
|
|
111
|
+
// Subsequent headings could be subtitles
|
|
112
|
+
element.classList.add('zCard-subtitle');
|
|
113
|
+
this.logger.log(`[CardRenderer] Applied zCard-subtitle to ${tagName}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else if (tagName === 'p') {
|
|
117
|
+
// Apply zCard-text to paragraphs
|
|
118
|
+
element.classList.add('zCard-text');
|
|
119
|
+
this.logger.log('[CardRenderer] Applied zCard-text to paragraph');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create a complete card from scratch
|
|
125
|
+
*
|
|
126
|
+
* @param {Object} options - Card configuration
|
|
127
|
+
* @param {string} [options.title] - Card title
|
|
128
|
+
* @param {string} [options.subtitle] - Card subtitle
|
|
129
|
+
* @param {string} [options.text] - Card text content
|
|
130
|
+
* @param {string} [options.variant] - Color variant (primary, success, info, etc.)
|
|
131
|
+
* @param {string} [options.header] - Header text
|
|
132
|
+
* @param {string} [options.footer] - Footer text
|
|
133
|
+
* @param {string} [options.classes] - Additional CSS classes
|
|
134
|
+
* @returns {HTMLElement} Complete card element
|
|
135
|
+
*/
|
|
136
|
+
createCard(options = {}) {
|
|
137
|
+
const {
|
|
138
|
+
title = '',
|
|
139
|
+
subtitle = '',
|
|
140
|
+
text = '',
|
|
141
|
+
variant = null,
|
|
142
|
+
header = null,
|
|
143
|
+
footer = null,
|
|
144
|
+
classes = ''
|
|
145
|
+
} = options;
|
|
146
|
+
|
|
147
|
+
// Create card container (using primitive)
|
|
148
|
+
const card = createDiv({ class: 'zCard' });
|
|
149
|
+
|
|
150
|
+
// Apply variant class
|
|
151
|
+
if (variant) {
|
|
152
|
+
card.classList.add(`zCard-${variant}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Apply additional classes
|
|
156
|
+
if (classes) {
|
|
157
|
+
const classList = classes.split(' ').filter(c => c.trim());
|
|
158
|
+
card.classList.add(...classList);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Add header if provided (using primitive)
|
|
162
|
+
if (header) {
|
|
163
|
+
const headerEl = createDiv({ class: 'zCard-header' });
|
|
164
|
+
headerEl.textContent = header;
|
|
165
|
+
card.appendChild(headerEl);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create card body (using primitive)
|
|
169
|
+
const cardBody = createDiv({ class: 'zCard-body' });
|
|
170
|
+
|
|
171
|
+
// Add title (using primitive)
|
|
172
|
+
if (title) {
|
|
173
|
+
const titleEl = createHeading(2, { class: 'zCard-title' });
|
|
174
|
+
titleEl.textContent = title;
|
|
175
|
+
cardBody.appendChild(titleEl);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Add subtitle (using primitive)
|
|
179
|
+
if (subtitle) {
|
|
180
|
+
const subtitleEl = createHeading(6, { class: 'zCard-subtitle zmb-2 zText-muted' });
|
|
181
|
+
subtitleEl.textContent = subtitle;
|
|
182
|
+
cardBody.appendChild(subtitleEl);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Add text content (using primitive)
|
|
186
|
+
if (text) {
|
|
187
|
+
const textEl = createParagraph({ class: 'zCard-text' });
|
|
188
|
+
textEl.textContent = text;
|
|
189
|
+
cardBody.appendChild(textEl);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
card.appendChild(cardBody);
|
|
193
|
+
|
|
194
|
+
// Add footer if provided (using primitive)
|
|
195
|
+
if (footer) {
|
|
196
|
+
const footerEl = createDiv({ class: 'zCard-footer zText-muted' });
|
|
197
|
+
footerEl.textContent = footer;
|
|
198
|
+
card.appendChild(footerEl);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.logger.log('[CardRenderer] Created card:', {title, variant, hasHeader: !!header, hasFooter: !!footer});
|
|
202
|
+
|
|
203
|
+
return card;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Extract card variant from color code
|
|
208
|
+
* @param {string} color - Color code (PRIMARY, SUCCESS, etc.)
|
|
209
|
+
* @returns {string|null} Card variant class or null
|
|
210
|
+
*/
|
|
211
|
+
getCardVariant(color) {
|
|
212
|
+
if (!color) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const variantMap = {
|
|
217
|
+
'PRIMARY': 'primary',
|
|
218
|
+
'SECONDARY': 'secondary',
|
|
219
|
+
'SUCCESS': 'success',
|
|
220
|
+
'INFO': 'info',
|
|
221
|
+
'WARNING': 'warning',
|
|
222
|
+
'DANGER': 'danger',
|
|
223
|
+
'LIGHT': 'light',
|
|
224
|
+
'DARK': 'dark'
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return variantMap[color.toUpperCase()] || null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Renderer - Renders `code` display events
|
|
3
|
+
*
|
|
4
|
+
* Handles the 'code' zDisplay event, which is the SSOT for code block
|
|
5
|
+
* rendering shared by:
|
|
6
|
+
* - zCode shorthand
|
|
7
|
+
* - zMD code fences (delegated from markdown_parser.py)
|
|
8
|
+
* - zTerminal preview (delegated from terminal_executor.py)
|
|
9
|
+
*
|
|
10
|
+
* Produces a <pre><code class="language-{lang}"> block.
|
|
11
|
+
* Prism.js (if loaded) will auto-highlight based on the language class.
|
|
12
|
+
*
|
|
13
|
+
* @module rendering/code_renderer
|
|
14
|
+
* @layer 3
|
|
15
|
+
* @pattern Strategy (single event type)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createLanguagePre } from '../primitives/semantic_element_primitive.js';
|
|
19
|
+
import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
|
|
20
|
+
|
|
21
|
+
export class CodeRenderer {
|
|
22
|
+
/**
|
|
23
|
+
* @param {Object} logger - Logger instance
|
|
24
|
+
*/
|
|
25
|
+
constructor(logger) {
|
|
26
|
+
this.logger = logger || console;
|
|
27
|
+
this.logger.debug('[CodeRenderer] Initialized');
|
|
28
|
+
|
|
29
|
+
const originalRenderCode = this.renderCode.bind(this);
|
|
30
|
+
this.renderCode = withErrorBoundary(originalRenderCode, {
|
|
31
|
+
component: 'CodeRenderer.renderCode',
|
|
32
|
+
logger: this.logger
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Render a code block element.
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} eventData
|
|
40
|
+
* @param {string} eventData.content - Raw code content
|
|
41
|
+
* @param {string} [eventData.language] - Programming language (e.g. 'python', 'js', 'zolo')
|
|
42
|
+
* @param {number} [eventData.indent] - Indentation level (unused in HTML; kept for symmetry)
|
|
43
|
+
* @returns {HTMLElement} <pre><code> element
|
|
44
|
+
*/
|
|
45
|
+
renderCode(eventData) {
|
|
46
|
+
const content = eventData.content || '';
|
|
47
|
+
const language = eventData.language || null;
|
|
48
|
+
|
|
49
|
+
this.logger.debug(`[CodeRenderer] renderCode: language=${language}, length=${content.length}`);
|
|
50
|
+
|
|
51
|
+
const attrs = {};
|
|
52
|
+
if (eventData.zId || eventData._zId || eventData._id) {
|
|
53
|
+
attrs.id = eventData.zId || eventData._zId || eventData._id;
|
|
54
|
+
}
|
|
55
|
+
// _zClass is applied centrally by the orchestrator (SSOT); only legacy `class`
|
|
56
|
+
// is honoured here as a creation-time attribute.
|
|
57
|
+
if (eventData.class) {
|
|
58
|
+
attrs.class = eventData.class;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const element = createLanguagePre(language || 'text', content, attrs);
|
|
62
|
+
return element;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default CodeRenderer;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DL Renderer - Description List Rendering
|
|
3
|
+
*
|
|
4
|
+
* Renders HTML description lists (<dl>, <dt>, <dd>) from zDisplay events.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from zdisplay_renderer.js (Phase 9, Task 3.2)
|
|
7
|
+
*
|
|
8
|
+
* @module display/outputs/dl_renderer
|
|
9
|
+
* @layer L2 (Handling)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class DLRenderer {
|
|
13
|
+
constructor(logger) {
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Render a description list element
|
|
19
|
+
* @param {Object} event - DL event data
|
|
20
|
+
* @param {Array} event.items - Array of {term, desc} objects
|
|
21
|
+
* @param {string} event._zClass - Optional CSS classes
|
|
22
|
+
* @param {number} event.indent - Optional indent level
|
|
23
|
+
* @returns {HTMLElement} - Description list element
|
|
24
|
+
*/
|
|
25
|
+
render(event) {
|
|
26
|
+
const dl = document.createElement('dl');
|
|
27
|
+
|
|
28
|
+
// _zClass / _zStyle applied centrally by the orchestrator (SSOT) on this <dl>.
|
|
29
|
+
|
|
30
|
+
// Apply indent as left margin
|
|
31
|
+
if (event.indent && event.indent > 0) {
|
|
32
|
+
dl.style.marginLeft = `${event.indent}rem`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Render description list items
|
|
36
|
+
const items = event.items || [];
|
|
37
|
+
items.forEach(item => {
|
|
38
|
+
// Create <dt> for the term
|
|
39
|
+
const dt = document.createElement('dt');
|
|
40
|
+
const termContent = item.term || '';
|
|
41
|
+
dt.innerHTML = this._sanitizeHTML(this._decodeUnicodeEscapes(termContent));
|
|
42
|
+
dl.appendChild(dt);
|
|
43
|
+
|
|
44
|
+
// Create <dd> for description(s)
|
|
45
|
+
// desc can be a string or an array of strings
|
|
46
|
+
const descriptions = Array.isArray(item.desc) ? item.desc : [item.desc];
|
|
47
|
+
descriptions.forEach(desc => {
|
|
48
|
+
const dd = document.createElement('dd');
|
|
49
|
+
const descContent = desc || '';
|
|
50
|
+
dd.innerHTML = this._sanitizeHTML(this._decodeUnicodeEscapes(descContent));
|
|
51
|
+
dl.appendChild(dd);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return dl;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Decode Unicode escape sequences to actual characters
|
|
60
|
+
* Supports: \uXXXX (standard) and \UXXXXXXXX (extended) formats
|
|
61
|
+
*
|
|
62
|
+
* Note: Basic escape sequences (\n, \t, etc.) are handled by JSON.parse()
|
|
63
|
+
* automatically when receiving data from backend. We only need to decode
|
|
64
|
+
* custom Unicode formats that JSON doesn't handle.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} text - Text containing Unicode escapes
|
|
67
|
+
* @returns {string} - Decoded text
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
_decodeUnicodeEscapes(text) {
|
|
71
|
+
if (!text || typeof text !== 'string') return text;
|
|
72
|
+
|
|
73
|
+
// Replace \uXXXX format (standard 4-digit Unicode escape)
|
|
74
|
+
text = text.replace(/\\u([0-9A-Fa-f]{4})/g, (match, hexCode) => {
|
|
75
|
+
return String.fromCodePoint(parseInt(hexCode, 16));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Replace \UXXXXXXXX format (extended 4-8 digit for supplementary characters & emojis)
|
|
79
|
+
text = text.replace(/\\U([0-9A-Fa-f]{4,8})/g, (match, hexCode) => {
|
|
80
|
+
return String.fromCodePoint(parseInt(hexCode, 16));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return text;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Sanitize HTML to prevent XSS attacks
|
|
88
|
+
* Allows common safe tags: <strong>, <em>, <code>, <a>, <br>, <span>
|
|
89
|
+
*
|
|
90
|
+
* @param {string} html - HTML string to sanitize
|
|
91
|
+
* @returns {string} - Sanitized HTML
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
_sanitizeHTML(html) {
|
|
95
|
+
if (!html) {
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Allow common safe tags: <strong>, <em>, <code>, <a>, <br>
|
|
100
|
+
// This is a basic sanitizer - for production, consider using DOMPurify
|
|
101
|
+
const allowedTags = ['strong', 'em', 'code', 'a', 'br', 'span'];
|
|
102
|
+
|
|
103
|
+
// Create a temporary div to parse HTML
|
|
104
|
+
const temp = document.createElement('div');
|
|
105
|
+
temp.innerHTML = html;
|
|
106
|
+
|
|
107
|
+
// Remove script tags and event handlers
|
|
108
|
+
const scripts = temp.querySelectorAll('script');
|
|
109
|
+
scripts.forEach(script => script.remove());
|
|
110
|
+
|
|
111
|
+
// Remove event handler attributes (onclick, onerror, etc.)
|
|
112
|
+
const allElements = temp.querySelectorAll('*');
|
|
113
|
+
allElements.forEach(el => {
|
|
114
|
+
Array.from(el.attributes).forEach(attr => {
|
|
115
|
+
if (attr.name.startsWith('on')) {
|
|
116
|
+
el.removeAttribute(attr.name);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Remove elements not in allowed list (except text nodes)
|
|
121
|
+
if (!allowedTags.includes(el.tagName.toLowerCase())) {
|
|
122
|
+
// Keep the text content but remove the tag
|
|
123
|
+
const textContent = el.textContent;
|
|
124
|
+
const textNode = document.createTextNode(textContent);
|
|
125
|
+
el.parentNode.replaceChild(textNode, el);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return temp.innerHTML;
|
|
130
|
+
}
|
|
131
|
+
}
|