@trustquery/browser 0.1.0

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.
@@ -0,0 +1,219 @@
1
+ // BubbleManager - Handles hover bubble tooltips for matched words
2
+
3
+ export default class BubbleManager {
4
+ /**
5
+ * Create bubble manager
6
+ * @param {Object} options - Configuration
7
+ */
8
+ constructor(options = {}) {
9
+ this.options = {
10
+ bubbleDelay: options.bubbleDelay || 200,
11
+ styleManager: options.styleManager || null,
12
+ commandHandlers: options.commandHandlers || null,
13
+ ...options
14
+ };
15
+
16
+ this.currentBubble = null;
17
+ this.hoverTimeout = null;
18
+
19
+ console.log('[BubbleManager] Initialized');
20
+ }
21
+
22
+ /**
23
+ * Show bubble for a match element
24
+ * @param {HTMLElement} matchEl - Match element
25
+ * @param {Object} matchData - Match data
26
+ */
27
+ showBubble(matchEl, matchData) {
28
+ // Remove any existing bubble
29
+ this.hideBubble();
30
+
31
+ // Get bubble content
32
+ const content = this.getBubbleContent(matchData);
33
+
34
+ if (!content) {
35
+ return; // No content to show
36
+ }
37
+
38
+ // Create bubble
39
+ const bubble = document.createElement('div');
40
+ bubble.className = 'tq-bubble';
41
+
42
+ // Add header container based on message-state
43
+ const messageState = matchData.intent?.handler?.['message-state'] || 'info';
44
+ this.createBubbleHeader(bubble, messageState);
45
+
46
+ // Add content container
47
+ const contentContainer = document.createElement('div');
48
+ contentContainer.className = 'tq-bubble-content';
49
+ contentContainer.innerHTML = content;
50
+ bubble.appendChild(contentContainer);
51
+
52
+ // Apply inline styles via StyleManager
53
+ if (this.options.styleManager) {
54
+ this.options.styleManager.applyBubbleStyles(bubble);
55
+ }
56
+
57
+ // Add to document
58
+ document.body.appendChild(bubble);
59
+ this.currentBubble = bubble;
60
+
61
+ // Position bubble relative to match element
62
+ this.positionBubble(bubble, matchEl);
63
+
64
+ // Auto-hide when mouse leaves bubble
65
+ bubble.addEventListener('mouseleave', () => {
66
+ this.hideBubble();
67
+ });
68
+
69
+ console.log('[BubbleManager] Bubble shown for:', matchData.text);
70
+ }
71
+
72
+ /**
73
+ * Create header container for bubble
74
+ * @param {HTMLElement} bubble - Bubble element
75
+ * @param {string} messageState - Message state (error, warning, info)
76
+ */
77
+ createBubbleHeader(bubble, messageState) {
78
+ const headerContainer = document.createElement('div');
79
+ headerContainer.className = 'bubble-header-container';
80
+ headerContainer.setAttribute('data-type', messageState);
81
+
82
+ // Create image
83
+ const img = document.createElement('img');
84
+ img.src = `./assets/trustquery-${messageState}.svg`;
85
+ img.style.height = '24px';
86
+ img.style.width = 'auto';
87
+
88
+ // Create text span
89
+ const span = document.createElement('span');
90
+ const textMap = {
91
+ 'error': 'TrustQuery Stop',
92
+ 'warning': 'TrustQuery Clarify',
93
+ 'info': 'TrustQuery Quick Link'
94
+ };
95
+ span.textContent = textMap[messageState] || 'TrustQuery';
96
+
97
+ // Append to header
98
+ headerContainer.appendChild(img);
99
+ headerContainer.appendChild(span);
100
+
101
+ // Apply styles to header via StyleManager
102
+ if (this.options.styleManager) {
103
+ this.options.styleManager.applyBubbleHeaderStyles(headerContainer, messageState);
104
+ }
105
+
106
+ bubble.appendChild(headerContainer);
107
+ }
108
+
109
+ /**
110
+ * Hide current bubble
111
+ */
112
+ hideBubble() {
113
+ if (this.currentBubble) {
114
+ this.currentBubble.remove();
115
+ this.currentBubble = null;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Get bubble content using command handler
121
+ * @param {Object} matchData - Match data including command and intent
122
+ * @returns {string|null} HTML content or null
123
+ */
124
+ getBubbleContent(matchData) {
125
+ // Check for new simplified format first (intent.handler.message)
126
+ if (matchData.intent && matchData.intent.handler) {
127
+ const handler = matchData.intent.handler;
128
+ const message = handler.message || handler['message-content'] || matchData.intent.description;
129
+
130
+ if (message) {
131
+ // Return just the message text - header is added separately
132
+ return this.escapeHtml(message);
133
+ }
134
+ }
135
+
136
+ // Use command handler if available (legacy support)
137
+ if (this.options.commandHandlers && matchData.commandType) {
138
+ const content = this.options.commandHandlers.getBubbleContent(matchData.commandType, matchData);
139
+ if (content) {
140
+ return content;
141
+ }
142
+ }
143
+
144
+ // Fallback to legacy method
145
+ const command = matchData.command || {};
146
+
147
+ if (command.content) {
148
+ return command.content;
149
+ }
150
+
151
+ if (command.bubbleContent) {
152
+ return command.bubbleContent;
153
+ }
154
+
155
+ if (command.description) {
156
+ return `<div class="tq-bubble-description">${this.escapeHtml(command.description)}</div>`;
157
+ }
158
+
159
+ return null;
160
+ }
161
+
162
+ /**
163
+ * Position bubble relative to match element
164
+ * @param {HTMLElement} bubble - Bubble element
165
+ * @param {HTMLElement} matchEl - Match element
166
+ */
167
+ positionBubble(bubble, matchEl) {
168
+ const rect = matchEl.getBoundingClientRect();
169
+ const bubbleRect = bubble.getBoundingClientRect();
170
+
171
+ // Position above match by default (since input is at bottom)
172
+ let top = rect.top + window.scrollY - bubbleRect.height - 8;
173
+ let left = rect.left + window.scrollX;
174
+
175
+ // If bubble goes off top edge, position below instead
176
+ if (top < window.scrollY) {
177
+ top = rect.bottom + window.scrollY + 8;
178
+ }
179
+
180
+ // Check if bubble goes off right edge
181
+ if (left + bubbleRect.width > window.innerWidth) {
182
+ left = window.innerWidth - bubbleRect.width - 10;
183
+ }
184
+
185
+ bubble.style.top = `${top}px`;
186
+ bubble.style.left = `${left}px`;
187
+ }
188
+
189
+ /**
190
+ * Escape HTML
191
+ * @param {string} text - Text to escape
192
+ * @returns {string} Escaped text
193
+ */
194
+ escapeHtml(text) {
195
+ const div = document.createElement('div');
196
+ div.textContent = text;
197
+ return div.innerHTML;
198
+ }
199
+
200
+ /**
201
+ * Cleanup
202
+ */
203
+ cleanup() {
204
+ this.hideBubble();
205
+
206
+ if (this.hoverTimeout) {
207
+ clearTimeout(this.hoverTimeout);
208
+ this.hoverTimeout = null;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Destroy
214
+ */
215
+ destroy() {
216
+ this.cleanup();
217
+ console.log('[BubbleManager] Destroyed');
218
+ }
219
+ }
@@ -0,0 +1,350 @@
1
+ // CommandHandlers - Handler registry for different command types
2
+ // Each command type has specific styling and behavior
3
+
4
+ export class CommandHandlerRegistry {
5
+ constructor() {
6
+ this.handlers = new Map();
7
+ this.registerDefaultHandlers();
8
+ }
9
+
10
+ /**
11
+ * Register default handlers
12
+ */
13
+ registerDefaultHandlers() {
14
+ this.register('not-allowed', new NotAllowedHandler());
15
+ this.register('show-warning', new ShowWarningHandler());
16
+ this.register('user-select-oneOf-and-warn', new UserSelectWithWarningHandler());
17
+ this.register('user-select-oneOf', new UserSelectHandler());
18
+ this.register('api-json-table', new ApiJsonTableHandler());
19
+ this.register('api-md-table', new ApiMdTableHandler());
20
+ }
21
+
22
+ /**
23
+ * Register a handler
24
+ * @param {string} commandType - Command type (e.g., 'not-allowed', 'show-warning')
25
+ * @param {CommandHandler} handler - Handler instance
26
+ */
27
+ register(commandType, handler) {
28
+ this.handlers.set(commandType, handler);
29
+ console.log(`[CommandHandlerRegistry] Registered handler: ${commandType}`);
30
+ }
31
+
32
+ /**
33
+ * Get handler for a command type
34
+ * @param {string} commandType - Command type
35
+ * @returns {CommandHandler|null} Handler or null
36
+ */
37
+ getHandler(commandType) {
38
+ return this.handlers.get(commandType) || null;
39
+ }
40
+
41
+ /**
42
+ * Get styles for a command type (or based on message-state)
43
+ * @param {string} commandType - Command type
44
+ * @param {Object} matchData - Match data including intent/handler
45
+ * @returns {Object} Style configuration
46
+ */
47
+ getStyles(commandType, matchData = null) {
48
+ // If matchData is provided, check message-state first
49
+ if (matchData && matchData.intent && matchData.intent.handler) {
50
+ const messageState = matchData.intent.handler['message-state'];
51
+ if (messageState) {
52
+ return this.getStylesForMessageState(messageState);
53
+ }
54
+ }
55
+
56
+ // Fall back to command type handler
57
+ const handler = this.getHandler(commandType);
58
+ return handler ? handler.getStyles() : this.getDefaultStyles();
59
+ }
60
+
61
+ /**
62
+ * Get styles based on message-state
63
+ * @param {string} messageState - Message state (error, warning, info)
64
+ * @returns {Object} Style configuration
65
+ */
66
+ getStylesForMessageState(messageState) {
67
+ const stateStyles = {
68
+ 'error': {
69
+ backgroundColor: 'rgba(220, 38, 38, 0.15)', // Red
70
+ color: '#991b1b',
71
+ textDecoration: 'none',
72
+ borderBottom: '2px solid #dc2626',
73
+ borderRadius: '0',
74
+ cursor: 'not-allowed'
75
+ },
76
+ 'warning': {
77
+ backgroundColor: 'rgba(245, 158, 11, 0.15)', // Orange
78
+ color: '#92400e',
79
+ textDecoration: 'none',
80
+ borderBottom: '2px solid #f59e0b',
81
+ borderRadius: '0',
82
+ cursor: 'help'
83
+ },
84
+ 'info': {
85
+ backgroundColor: 'rgba(16, 185, 129, 0.15)', // Green
86
+ color: '#065f46',
87
+ textDecoration: 'none',
88
+ borderBottom: '2px solid #10b981',
89
+ borderRadius: '0',
90
+ cursor: 'pointer'
91
+ }
92
+ };
93
+
94
+ return stateStyles[messageState] || this.getDefaultStyles();
95
+ }
96
+
97
+ /**
98
+ * Get bubble content for a match
99
+ * @param {string} commandType - Command type
100
+ * @param {Object} matchData - Match data including intent info
101
+ * @returns {string} HTML content for bubble
102
+ */
103
+ getBubbleContent(commandType, matchData) {
104
+ const handler = this.getHandler(commandType);
105
+ return handler ? handler.getBubbleContent(matchData) : null;
106
+ }
107
+
108
+ /**
109
+ * Get default styles (fallback)
110
+ */
111
+ getDefaultStyles() {
112
+ return {
113
+ backgroundColor: 'rgba(74, 144, 226, 0.15)',
114
+ color: '#2b6cb0',
115
+ textDecoration: 'none',
116
+ borderBottom: 'none'
117
+ };
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Base handler class
123
+ */
124
+ class CommandHandler {
125
+ getStyles() {
126
+ return {};
127
+ }
128
+
129
+ getBubbleContent(matchData) {
130
+ return null;
131
+ }
132
+
133
+ shouldBlockSubmit(matchData) {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Handler for not-allowed commands (errors/forbidden)
140
+ * Red background, red underline
141
+ */
142
+ class NotAllowedHandler extends CommandHandler {
143
+ getStyles() {
144
+ return {
145
+ backgroundColor: 'rgba(220, 38, 38, 0.15)', // Red background
146
+ color: '#991b1b', // Dark red text
147
+ textDecoration: 'none',
148
+ borderBottom: '2px solid #dc2626', // Red underline
149
+ borderRadius: '0', // No radius to avoid curved bottom border
150
+ cursor: 'not-allowed'
151
+ };
152
+ }
153
+
154
+ getBubbleContent(matchData) {
155
+ const intent = matchData.intent || {};
156
+ const handler = intent.handler || {};
157
+
158
+ const description = intent.description || 'Not allowed';
159
+ const message = handler['message-content'] || description;
160
+
161
+ return `
162
+ <div style="color: #991b1b;">
163
+ <div style="font-weight: 600; margin-bottom: 4px; display: flex; align-items: center;">
164
+ <span style="margin-right: 6px;">⛔</span>
165
+ Not Allowed
166
+ </div>
167
+ <div style="font-size: 12px; line-height: 1.4;">
168
+ ${this.escapeHtml(message)}
169
+ </div>
170
+ </div>
171
+ `;
172
+ }
173
+
174
+ shouldBlockSubmit(matchData) {
175
+ const handler = matchData.intent?.handler || {};
176
+ return handler['block-submit'] === true;
177
+ }
178
+
179
+ escapeHtml(text) {
180
+ const div = document.createElement('div');
181
+ div.textContent = text;
182
+ return div.innerHTML;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Handler for show-warning commands
188
+ * Yellow/orange background, orange underline
189
+ */
190
+ class ShowWarningHandler extends CommandHandler {
191
+ getStyles() {
192
+ return {
193
+ backgroundColor: 'rgba(245, 158, 11, 0.15)', // Amber/orange background
194
+ color: '#92400e', // Dark amber text
195
+ textDecoration: 'none',
196
+ borderBottom: '2px solid #f59e0b', // Orange underline
197
+ borderRadius: '0', // No radius to avoid curved bottom border
198
+ cursor: 'help'
199
+ };
200
+ }
201
+
202
+ getBubbleContent(matchData) {
203
+ const intent = matchData.intent || {};
204
+ const handler = intent.handler || {};
205
+
206
+ const description = intent.description || 'Warning';
207
+ const message = handler['message-content'] || description;
208
+
209
+ return `
210
+ <div style="color: #92400e;">
211
+ <div style="font-weight: 600; margin-bottom: 4px; display: flex; align-items: center;">
212
+ <span style="margin-right: 6px;">⚠️</span>
213
+ Warning
214
+ </div>
215
+ <div style="font-size: 12px; line-height: 1.4;">
216
+ ${this.escapeHtml(message)}
217
+ </div>
218
+ </div>
219
+ `;
220
+ }
221
+
222
+ shouldBlockSubmit(matchData) {
223
+ const handler = matchData.intent?.handler || {};
224
+ return handler['block-submit'] === true;
225
+ }
226
+
227
+ escapeHtml(text) {
228
+ const div = document.createElement('div');
229
+ div.textContent = text;
230
+ return div.innerHTML;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Handler for user-select-oneOf-and-warn commands
236
+ * Shows warning + dropdown
237
+ */
238
+ class UserSelectWithWarningHandler extends CommandHandler {
239
+ getStyles() {
240
+ return {
241
+ backgroundColor: 'rgba(245, 158, 11, 0.15)',
242
+ color: '#92400e',
243
+ textDecoration: 'none',
244
+ borderBottom: '2px solid #f59e0b',
245
+ cursor: 'pointer'
246
+ };
247
+ }
248
+
249
+ getBubbleContent(matchData) {
250
+ // Will show dropdown on click instead of bubble
251
+ return null;
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Handler for user-select-oneOf commands
257
+ * Shows dropdown without warning
258
+ */
259
+ class UserSelectHandler extends CommandHandler {
260
+ getStyles() {
261
+ return {
262
+ backgroundColor: 'rgba(59, 130, 246, 0.15)', // Blue background
263
+ color: '#1e40af', // Dark blue text
264
+ textDecoration: 'none',
265
+ borderBottom: '2px solid #3b82f6', // Blue underline
266
+ cursor: 'pointer'
267
+ };
268
+ }
269
+
270
+ getBubbleContent(matchData) {
271
+ // Will show dropdown on click instead of bubble
272
+ return null;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Handler for api-json-table commands
278
+ */
279
+ class ApiJsonTableHandler extends CommandHandler {
280
+ getStyles() {
281
+ return {
282
+ backgroundColor: 'rgba(16, 185, 129, 0.15)', // Green background
283
+ color: '#065f46', // Dark green text
284
+ textDecoration: 'none',
285
+ borderBottom: '2px solid #10b981',
286
+ cursor: 'pointer'
287
+ };
288
+ }
289
+
290
+ getBubbleContent(matchData) {
291
+ const intent = matchData.intent || {};
292
+ const description = intent.description || 'Click to view data';
293
+
294
+ return `
295
+ <div style="color: #065f46;">
296
+ <div style="font-weight: 600; margin-bottom: 4px;">
297
+ 📊 Data Table
298
+ </div>
299
+ <div style="font-size: 12px;">
300
+ ${this.escapeHtml(description)}
301
+ </div>
302
+ </div>
303
+ `;
304
+ }
305
+
306
+ escapeHtml(text) {
307
+ const div = document.createElement('div');
308
+ div.textContent = text;
309
+ return div.innerHTML;
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Handler for api-md-table commands
315
+ */
316
+ class ApiMdTableHandler extends CommandHandler {
317
+ getStyles() {
318
+ return {
319
+ backgroundColor: 'rgba(16, 185, 129, 0.15)',
320
+ color: '#065f46',
321
+ textDecoration: 'none',
322
+ borderBottom: '2px solid #10b981',
323
+ cursor: 'pointer'
324
+ };
325
+ }
326
+
327
+ getBubbleContent(matchData) {
328
+ const intent = matchData.intent || {};
329
+ const description = intent.description || 'Click to view data';
330
+
331
+ return `
332
+ <div style="color: #065f46;">
333
+ <div style="font-weight: 600; margin-bottom: 4px;">
334
+ 📊 Data Table
335
+ </div>
336
+ <div style="font-size: 12px;">
337
+ ${this.escapeHtml(description)}
338
+ </div>
339
+ </div>
340
+ `;
341
+ }
342
+
343
+ escapeHtml(text) {
344
+ const div = document.createElement('div');
345
+ div.textContent = text;
346
+ return div.innerHTML;
347
+ }
348
+ }
349
+
350
+ export default CommandHandlerRegistry;