@cognior/iap-sdk 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.
Potentially problematic release.
This version of @cognior/iap-sdk might be problematic. Click here for more details.
- package/.github/copilot-instructions.md +95 -0
- package/README.md +79 -0
- package/TRACKING.md +105 -0
- package/USER_CONTEXT_README.md +284 -0
- package/package.json +154 -0
- package/src/config.ts +25 -0
- package/src/core/flowEngine.ts +1833 -0
- package/src/core/triggerManager.ts +1011 -0
- package/src/experiences/banner.ts +366 -0
- package/src/experiences/beacon.ts +668 -0
- package/src/experiences/hotspotTour.ts +654 -0
- package/src/experiences/hotspots.ts +566 -0
- package/src/experiences/modal.ts +1337 -0
- package/src/experiences/modalSequence.ts +1247 -0
- package/src/experiences/popover.ts +652 -0
- package/src/experiences/registry.ts +21 -0
- package/src/experiences/survey.ts +1639 -0
- package/src/experiences/taskList.ts +625 -0
- package/src/experiences/tooltip.ts +740 -0
- package/src/experiences/types.ts +395 -0
- package/src/experiences/walkthrough.ts +670 -0
- package/src/flow-sequence.ts +177 -0
- package/src/flows.ts +512 -0
- package/src/http.ts +61 -0
- package/src/index.ts +355 -0
- package/src/services/flowManager.ts +905 -0
- package/src/services/flowNormalizer.ts +74 -0
- package/src/services/locationContextService.ts +189 -0
- package/src/services/pageContextService.ts +221 -0
- package/src/services/userContextService.ts +286 -0
- package/src/state/appState.ts +0 -0
- package/src/state/hooks.ts +0 -0
- package/src/state/index.ts +0 -0
- package/src/state/migration.ts +0 -0
- package/src/state/store.ts +0 -0
- package/src/styles/banner.css.ts +0 -0
- package/src/styles/hotspot.css.ts +0 -0
- package/src/styles/hotspotTour.css.ts +0 -0
- package/src/styles/modal.css.ts +564 -0
- package/src/styles/survey.css.ts +1013 -0
- package/src/styles/taskList.css.ts +0 -0
- package/src/styles/tooltip.css.ts +149 -0
- package/src/styles/walkthrough.css.ts +0 -0
- package/src/tourUtils.ts +0 -0
- package/src/tracking.ts +223 -0
- package/src/utils/debounce.ts +66 -0
- package/src/utils/eventSequenceValidator.ts +124 -0
- package/src/utils/flowTrackingSystem.ts +524 -0
- package/src/utils/idGenerator.ts +155 -0
- package/src/utils/immediateValidationPrevention.ts +184 -0
- package/src/utils/normalize.ts +50 -0
- package/src/utils/privacyManager.ts +166 -0
- package/src/utils/ruleEvaluator.ts +199 -0
- package/src/utils/sanitize.ts +79 -0
- package/src/utils/selectors.ts +107 -0
- package/src/utils/stepExecutor.ts +345 -0
- package/src/utils/triggerNormalizer.ts +149 -0
- package/src/utils/validationInterceptor.ts +650 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
// src/experiences/modalSequence.ts
|
|
2
|
+
// Multi-step modal sequence renderer with trigger support
|
|
3
|
+
|
|
4
|
+
import { sanitizeHtml } from "../utils/sanitize";
|
|
5
|
+
import { register, getRenderer } from "./registry";
|
|
6
|
+
import { resolveSelector } from "../utils/selectors";
|
|
7
|
+
import { modalCssText } from "../styles/modal.css";
|
|
8
|
+
import { createRuleEvaluationListener } from "../utils/ruleEvaluator";
|
|
9
|
+
import type {
|
|
10
|
+
ModalSequencePayload,
|
|
11
|
+
ModalSequenceStep,
|
|
12
|
+
ModalContent,
|
|
13
|
+
ModalSize,
|
|
14
|
+
TooltipPayload,
|
|
15
|
+
PopoverPayload,
|
|
16
|
+
CompletionTracker
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
type ModalSeqFlow = { id: string; type: "modalSequence"; payload: ModalSequencePayload };
|
|
20
|
+
|
|
21
|
+
export function registerModalSequence() {
|
|
22
|
+
register("modalSequence", renderModalSequence);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function renderModalSequence(flow: ModalSeqFlow): Promise<void> {
|
|
26
|
+
console.debug("[DAP] renderModalSequence called with flow:", flow);
|
|
27
|
+
console.debug("[DAP] ModalSequence payload:", flow.payload);
|
|
28
|
+
|
|
29
|
+
const { payload, id } = flow;
|
|
30
|
+
|
|
31
|
+
if (!payload || !Array.isArray(payload.steps) || payload.steps.length === 0) {
|
|
32
|
+
console.warn("[DAP] Modal sequence has no steps");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Extract completion tracker
|
|
37
|
+
const completionTracker = payload._completionTracker;
|
|
38
|
+
const isMultiStep = payload.steps.length > 1;
|
|
39
|
+
|
|
40
|
+
// Ensure CSS is injected
|
|
41
|
+
ensureStyles();
|
|
42
|
+
|
|
43
|
+
// State management
|
|
44
|
+
let currentStepIndex = payload.startAt || 0;
|
|
45
|
+
let currentTriggerListeners: (() => void)[] = [];
|
|
46
|
+
let currentStepState: 'idle' | 'waiting-for-trigger' | 'rendered' | 'completed' = 'idle';
|
|
47
|
+
let activeStepTriggered = false;
|
|
48
|
+
|
|
49
|
+
// UI cleanup functions
|
|
50
|
+
let modalShell: ReturnType<typeof createModalShell> | null = null;
|
|
51
|
+
let tooltipCleanup: (() => void) | null = null;
|
|
52
|
+
let popoverCleanup: (() => void) | null = null;
|
|
53
|
+
|
|
54
|
+
// Focus management
|
|
55
|
+
const prevActive = document.activeElement as HTMLElement | null;
|
|
56
|
+
|
|
57
|
+
// Keyboard handler
|
|
58
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
59
|
+
if (e.key === "Escape") {
|
|
60
|
+
closeAll();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
64
|
+
|
|
65
|
+
// Start with first step
|
|
66
|
+
await evaluateAndRenderStep(currentStepIndex);
|
|
67
|
+
|
|
68
|
+
async function evaluateAndRenderStep(stepIndex: number) {
|
|
69
|
+
const step = payload.steps[stepIndex];
|
|
70
|
+
if (!step) return;
|
|
71
|
+
|
|
72
|
+
const stepId = step.stepId || `step-${stepIndex + 1}`;
|
|
73
|
+
|
|
74
|
+
console.debug(`[DAP] Evaluating step ${stepIndex}:`, {
|
|
75
|
+
stepId,
|
|
76
|
+
elementSelector: step.elementSelector,
|
|
77
|
+
elementTrigger: step.elementTrigger,
|
|
78
|
+
kind: step.kind
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Determine if this is a trigger-based step
|
|
82
|
+
const isTriggerBasedStep = Boolean(step.elementSelector && step.elementTrigger);
|
|
83
|
+
|
|
84
|
+
if (isTriggerBasedStep) {
|
|
85
|
+
// Setup trigger and wait
|
|
86
|
+
await setupStepTrigger(stepIndex, step, stepId);
|
|
87
|
+
currentStepState = 'waiting-for-trigger';
|
|
88
|
+
console.debug(`[DAP] Step ${stepIndex} waiting for trigger`);
|
|
89
|
+
} else {
|
|
90
|
+
// Render immediately
|
|
91
|
+
await renderStepExperience(stepIndex, step, stepId, true);
|
|
92
|
+
currentStepState = 'rendered';
|
|
93
|
+
console.debug(`[DAP] Step ${stepIndex} rendered immediately`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function setupStepTrigger(stepIndex: number, step: ModalSequenceStep, stepId: string) {
|
|
98
|
+
if (!step.elementSelector || !step.elementTrigger) return;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Wait for target element
|
|
102
|
+
const targetElement = await waitForElement(step.elementSelector);
|
|
103
|
+
if (!targetElement) {
|
|
104
|
+
console.warn(`[DAP] Target element not found: ${step.elementSelector}`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Setup trigger listener
|
|
109
|
+
const triggerEvent = normalizeTrigger(step.elementTrigger);
|
|
110
|
+
const triggerHandler = () => {
|
|
111
|
+
if (activeStepTriggered) return; // Prevent duplicate triggers
|
|
112
|
+
activeStepTriggered = true;
|
|
113
|
+
|
|
114
|
+
console.debug(`[DAP] Step ${stepIndex} triggered by ${triggerEvent}`);
|
|
115
|
+
renderStepExperience(stepIndex, step, stepId, false); // false = no navigation for trigger steps
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
targetElement.addEventListener(triggerEvent, triggerHandler);
|
|
119
|
+
|
|
120
|
+
// Store cleanup function
|
|
121
|
+
const cleanup = () => {
|
|
122
|
+
targetElement.removeEventListener(triggerEvent, triggerHandler);
|
|
123
|
+
};
|
|
124
|
+
currentTriggerListeners.push(cleanup);
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`[DAP] Error setting up trigger for step ${stepIndex}:`, error);
|
|
128
|
+
// Auto-advance on trigger setup failure
|
|
129
|
+
if (stepIndex < payload.steps.length - 1) {
|
|
130
|
+
setTimeout(() => transitionToStep(stepIndex + 1), 100);
|
|
131
|
+
} else {
|
|
132
|
+
closeAll();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function renderStepExperience(stepIndex: number, step: ModalSequenceStep, stepId: string, showNavigation: boolean) {
|
|
138
|
+
// Clean up previous UI
|
|
139
|
+
cleanupCurrentStep();
|
|
140
|
+
|
|
141
|
+
console.debug(`[DAP] Rendering step ${stepIndex} (${step.kind})`);
|
|
142
|
+
|
|
143
|
+
switch (step.kind) {
|
|
144
|
+
case "modal":
|
|
145
|
+
await renderModalStep(step, showNavigation);
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case "tooltip":
|
|
149
|
+
if (step.tooltip) {
|
|
150
|
+
await renderTooltipStep(step.tooltip);
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case "popover":
|
|
155
|
+
if (step.popover) {
|
|
156
|
+
await renderPopoverStep(step.popover);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case "survey":
|
|
161
|
+
// Survey implementation would go here
|
|
162
|
+
console.warn("[DAP] Survey step not implemented");
|
|
163
|
+
break;
|
|
164
|
+
|
|
165
|
+
case "rule":
|
|
166
|
+
// Rule processing - delegate to FlowEngine
|
|
167
|
+
if (step.rule) {
|
|
168
|
+
await renderRuleStep(step.rule, stepIndex);
|
|
169
|
+
} else {
|
|
170
|
+
console.warn("[DAP] Rule step has no rule data");
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
default:
|
|
175
|
+
console.warn(`[DAP] Unknown step kind: ${(step as any).kind}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
currentStepState = 'rendered';
|
|
179
|
+
|
|
180
|
+
// Auto-advance for trigger-based steps after brief delay
|
|
181
|
+
if (!showNavigation && stepIndex < payload.steps.length - 1) {
|
|
182
|
+
setTimeout(() => transitionToStep(stepIndex + 1), 2000);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function renderModalStep(step: ModalSequenceStep, showNavigation: boolean) {
|
|
187
|
+
modalShell = createModalShell(step.size);
|
|
188
|
+
|
|
189
|
+
// Header - always populate the title area
|
|
190
|
+
const header = modalShell.modal.querySelector(".dap-header-bar") as HTMLElement;
|
|
191
|
+
if (step.title) {
|
|
192
|
+
const title = document.createElement("h2");
|
|
193
|
+
title.className = "dap-header-text";
|
|
194
|
+
title.textContent = step.title;
|
|
195
|
+
header.insertBefore(title, header.firstChild);
|
|
196
|
+
} else {
|
|
197
|
+
// Add empty space to maintain header layout
|
|
198
|
+
const title = document.createElement("h2");
|
|
199
|
+
title.className = "dap-header-text";
|
|
200
|
+
title.style.visibility = "hidden";
|
|
201
|
+
title.innerHTML = " ";
|
|
202
|
+
header.insertBefore(title, header.firstChild);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Body content
|
|
206
|
+
const body = modalShell.modal.querySelector(".dap-modal-body") as HTMLElement;
|
|
207
|
+
if (step.body && Array.isArray(step.body)) {
|
|
208
|
+
step.body.forEach(content => {
|
|
209
|
+
const contentEl = renderModalContent(content, step);
|
|
210
|
+
if (contentEl) body.appendChild(contentEl);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Footer - always show footer for consistent layout
|
|
215
|
+
const footer = modalShell.modal.querySelector(".dap-modal-footer") as HTMLElement;
|
|
216
|
+
|
|
217
|
+
if (step.footerText) {
|
|
218
|
+
const footerText = document.createElement("p");
|
|
219
|
+
footerText.className = "dap-footer-text";
|
|
220
|
+
footerText.innerHTML = sanitizeHtml(step.footerText);
|
|
221
|
+
footer.appendChild(footerText);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (showNavigation && isMultiStep) {
|
|
225
|
+
const nav = createNavigationButtons();
|
|
226
|
+
footer.appendChild(nav);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Add to DOM
|
|
230
|
+
document.documentElement.appendChild(modalShell.overlay);
|
|
231
|
+
|
|
232
|
+
// Event handlers
|
|
233
|
+
modalShell.overlay.addEventListener("click", (e) => {
|
|
234
|
+
if (e.target === modalShell!.overlay) {
|
|
235
|
+
closeAll();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const closeBtn = modalShell.modal.querySelector(".dap-modal-close") as HTMLButtonElement;
|
|
240
|
+
if (closeBtn) {
|
|
241
|
+
closeBtn.addEventListener("click", closeAll);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function renderTooltipStep(tooltipPayload: TooltipPayload) {
|
|
246
|
+
try {
|
|
247
|
+
const tooltipRenderer = getRenderer("tooltip");
|
|
248
|
+
if (tooltipRenderer) {
|
|
249
|
+
await tooltipRenderer({
|
|
250
|
+
id: `${id}-tooltip`,
|
|
251
|
+
type: "tooltip",
|
|
252
|
+
payload: tooltipPayload
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error("[DAP] Error rendering tooltip step:", error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function renderPopoverStep(popoverPayload: PopoverPayload) {
|
|
261
|
+
try {
|
|
262
|
+
const popoverRenderer = getRenderer("popover");
|
|
263
|
+
if (popoverRenderer) {
|
|
264
|
+
await popoverRenderer({
|
|
265
|
+
id: `${id}-popover`,
|
|
266
|
+
type: "popover",
|
|
267
|
+
payload: popoverPayload
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error("[DAP] Error rendering popover step:", error);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function renderRuleStep(rulePayload: any, stepIndex: number) {
|
|
276
|
+
console.debug("[DAP] === RULE STEP PROCESSING START ===");
|
|
277
|
+
console.debug("[DAP] Step Index:", stepIndex);
|
|
278
|
+
console.debug("[DAP] Rule Payload:", rulePayload);
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const { inputSelector, rules } = rulePayload;
|
|
282
|
+
|
|
283
|
+
console.debug("[DAP] Extracted inputSelector:", inputSelector);
|
|
284
|
+
console.debug("[DAP] Extracted rules:", rules);
|
|
285
|
+
|
|
286
|
+
if (!inputSelector || !rules || rules.length === 0) {
|
|
287
|
+
console.warn("[DAP] Rule step missing inputSelector or rules");
|
|
288
|
+
console.warn("[DAP] inputSelector:", inputSelector);
|
|
289
|
+
console.warn("[DAP] rules:", rules);
|
|
290
|
+
// Auto-advance to next step
|
|
291
|
+
if (stepIndex < payload.steps.length - 1) {
|
|
292
|
+
setTimeout(() => transitionToStep(stepIndex + 1), 100);
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.debug(`[DAP] Setting up rule evaluation listener for selector: ${inputSelector}`);
|
|
298
|
+
console.debug(`[DAP] Number of rules: ${rules.length}`);
|
|
299
|
+
rules.forEach((rule: any, index: number) => {
|
|
300
|
+
console.debug(`[DAP] Rule ${index + 1}:`, rule);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Find the target element for input monitoring
|
|
304
|
+
console.debug(`[DAP] Resolving selector: ${inputSelector}`);
|
|
305
|
+
const targetElement = resolveSelector(inputSelector);
|
|
306
|
+
console.debug(`[DAP] Target element found:`, targetElement);
|
|
307
|
+
|
|
308
|
+
if (!targetElement) {
|
|
309
|
+
console.warn(`[DAP] Could not find target element for selector: ${inputSelector}`);
|
|
310
|
+
console.warn(`[DAP] Available elements with similar selectors:`);
|
|
311
|
+
// Try to find similar elements for debugging
|
|
312
|
+
try {
|
|
313
|
+
const allInputs = document.querySelectorAll('input, select, textarea');
|
|
314
|
+
console.warn(`[DAP] Found ${allInputs.length} input elements on page`);
|
|
315
|
+
allInputs.forEach((el, i) => {
|
|
316
|
+
console.warn(`[DAP] Input ${i + 1}: ${el.tagName}${el.id ? '#' + el.id : ''}${el.className ? '.' + el.className.replace(/\s+/g, '.') : ''}`);
|
|
317
|
+
});
|
|
318
|
+
} catch (e) {
|
|
319
|
+
console.warn(`[DAP] Error listing input elements:`, e);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Auto-advance to next step
|
|
323
|
+
if (stepIndex < payload.steps.length - 1) {
|
|
324
|
+
setTimeout(() => transitionToStep(stepIndex + 1), 100);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
console.debug(`[DAP] Creating rule evaluation listener...`);
|
|
330
|
+
|
|
331
|
+
// Create rule evaluation listener
|
|
332
|
+
const eventHandler = createRuleEvaluationListener(
|
|
333
|
+
rules,
|
|
334
|
+
(nextFlowId: string) => {
|
|
335
|
+
console.debug(`[DAP] *** RULE EVALUATION TRIGGERED ***`);
|
|
336
|
+
console.debug(`[DAP] Rule evaluation triggered transition to flow: ${nextFlowId}`);
|
|
337
|
+
|
|
338
|
+
// Testing alert for rule evaluation
|
|
339
|
+
alert(`🎯 Rule Evaluated! Next Flow: ${nextFlowId}\n\nRule evaluation is working correctly.`);
|
|
340
|
+
|
|
341
|
+
// In modal sequence context, we'll just log this for now
|
|
342
|
+
// In a real implementation, this would trigger flow switching
|
|
343
|
+
console.info(`[DAP] Would transition to flow: ${nextFlowId}`);
|
|
344
|
+
|
|
345
|
+
// Auto-advance to next step after rule match
|
|
346
|
+
if (stepIndex < payload.steps.length - 1) {
|
|
347
|
+
console.debug(`[DAP] Auto-advancing to next step: ${stepIndex + 1}`);
|
|
348
|
+
setTimeout(() => transitionToStep(stepIndex + 1), 100);
|
|
349
|
+
} else {
|
|
350
|
+
console.debug(`[DAP] Rule step was the last step`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
console.debug(`[DAP] Attaching event listeners to target element...`);
|
|
356
|
+
|
|
357
|
+
// Attach event listener to the target element
|
|
358
|
+
targetElement.addEventListener('input', eventHandler);
|
|
359
|
+
targetElement.addEventListener('change', eventHandler);
|
|
360
|
+
|
|
361
|
+
console.debug(`[DAP] Event listeners attached for 'input' and 'change' events`);
|
|
362
|
+
|
|
363
|
+
// Create cleanup function
|
|
364
|
+
const cleanup = () => {
|
|
365
|
+
console.debug(`[DAP] Cleaning up rule step event listeners`);
|
|
366
|
+
targetElement.removeEventListener('input', eventHandler);
|
|
367
|
+
targetElement.removeEventListener('change', eventHandler);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Store cleanup function for later
|
|
371
|
+
currentTriggerListeners.push(cleanup);
|
|
372
|
+
|
|
373
|
+
console.debug("[DAP] Rule step listener active - waiting for user input");
|
|
374
|
+
console.debug(`[DAP] Target element type: ${targetElement.tagName}, value: "${(targetElement as HTMLInputElement).value || ''}"`);
|
|
375
|
+
console.debug("[DAP] === RULE STEP PROCESSING COMPLETE ===");
|
|
376
|
+
|
|
377
|
+
// Testing alert to confirm rule step setup
|
|
378
|
+
alert(`🔍 Rule Step Setup Complete!\n\nMonitoring element: ${inputSelector}\nRules: ${rules.length}\n\nTry interacting with the target element.`);
|
|
379
|
+
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error("[DAP] Error setting up rule step:", error);
|
|
382
|
+
if (error instanceof Error) {
|
|
383
|
+
console.error("[DAP] Stack trace:", error.stack);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// On error, try to continue to next step
|
|
387
|
+
if (stepIndex < payload.steps.length - 1) {
|
|
388
|
+
setTimeout(() => transitionToStep(stepIndex + 1), 100);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function createNavigationButtons(): HTMLElement {
|
|
394
|
+
const nav = document.createElement("div");
|
|
395
|
+
nav.className = "dap-modal-nav";
|
|
396
|
+
|
|
397
|
+
// Previous button
|
|
398
|
+
const prevBtn = document.createElement("button");
|
|
399
|
+
prevBtn.className = "dap-btn dap-btn-secondary";
|
|
400
|
+
prevBtn.textContent = "Previous";
|
|
401
|
+
prevBtn.disabled = currentStepIndex === 0;
|
|
402
|
+
prevBtn.addEventListener("click", goToPrevious);
|
|
403
|
+
|
|
404
|
+
// Step indicator
|
|
405
|
+
const indicator = document.createElement("span");
|
|
406
|
+
indicator.className = "dap-step-indicator";
|
|
407
|
+
indicator.textContent = `${currentStepIndex + 1} of ${payload.steps.length}`;
|
|
408
|
+
|
|
409
|
+
// Next button
|
|
410
|
+
const nextBtn = document.createElement("button");
|
|
411
|
+
nextBtn.className = "dap-btn dap-btn-primary";
|
|
412
|
+
nextBtn.textContent = currentStepIndex === payload.steps.length - 1 ? "Complete" : "Next";
|
|
413
|
+
nextBtn.addEventListener("click", goToNext);
|
|
414
|
+
|
|
415
|
+
nav.appendChild(prevBtn);
|
|
416
|
+
nav.appendChild(indicator);
|
|
417
|
+
nav.appendChild(nextBtn);
|
|
418
|
+
|
|
419
|
+
return nav;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function goToPrevious() {
|
|
423
|
+
if (currentStepIndex > 0) {
|
|
424
|
+
transitionToStep(currentStepIndex - 1);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function goToNext() {
|
|
429
|
+
if (currentStepIndex < payload.steps.length - 1) {
|
|
430
|
+
transitionToStep(currentStepIndex + 1);
|
|
431
|
+
} else {
|
|
432
|
+
closeAll();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function transitionToStep(newStepIndex: number) {
|
|
437
|
+
console.debug(`[DAP] Transitioning from step ${currentStepIndex} to step ${newStepIndex}`);
|
|
438
|
+
|
|
439
|
+
// Clean up current step
|
|
440
|
+
cleanupCurrentStep();
|
|
441
|
+
|
|
442
|
+
// Update state
|
|
443
|
+
const oldStepIndex = currentStepIndex;
|
|
444
|
+
currentStepIndex = newStepIndex;
|
|
445
|
+
activeStepTriggered = false;
|
|
446
|
+
currentStepState = 'idle';
|
|
447
|
+
|
|
448
|
+
// Notify step advancement
|
|
449
|
+
if (completionTracker?.onStepAdvance) {
|
|
450
|
+
const newStepId = payload.steps[newStepIndex]?.stepId || `step-${newStepIndex + 1}`;
|
|
451
|
+
completionTracker.onStepAdvance(newStepId);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Render new step
|
|
455
|
+
evaluateAndRenderStep(currentStepIndex);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function cleanupCurrentStep() {
|
|
459
|
+
console.debug(`[DAP] Cleaning up step ${currentStepIndex}`);
|
|
460
|
+
|
|
461
|
+
// Remove trigger listeners
|
|
462
|
+
currentTriggerListeners.forEach(cleanup => cleanup());
|
|
463
|
+
currentTriggerListeners = [];
|
|
464
|
+
|
|
465
|
+
// Clean up UI
|
|
466
|
+
if (modalShell) {
|
|
467
|
+
modalShell.overlay.remove();
|
|
468
|
+
modalShell = null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
tooltipCleanup?.();
|
|
472
|
+
tooltipCleanup = null;
|
|
473
|
+
|
|
474
|
+
popoverCleanup?.();
|
|
475
|
+
popoverCleanup = null;
|
|
476
|
+
|
|
477
|
+
// Reset state
|
|
478
|
+
currentStepState = 'idle';
|
|
479
|
+
activeStepTriggered = false;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function closeAll() {
|
|
483
|
+
console.debug("[DAP] Closing modal sequence");
|
|
484
|
+
|
|
485
|
+
cleanupCurrentStep();
|
|
486
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
487
|
+
prevActive?.focus();
|
|
488
|
+
|
|
489
|
+
// Signal completion
|
|
490
|
+
if (completionTracker?.onComplete) {
|
|
491
|
+
console.debug(`[DAP] Completing modal sequence flow: ${id}`);
|
|
492
|
+
completionTracker.onComplete();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function createModalShell(size?: string) {
|
|
498
|
+
const overlay = document.createElement("div");
|
|
499
|
+
overlay.className = "dap-modal-wrap";
|
|
500
|
+
|
|
501
|
+
const modal = document.createElement("div");
|
|
502
|
+
modal.className = "dap-modal";
|
|
503
|
+
|
|
504
|
+
// Apply size class if specified
|
|
505
|
+
if (size) {
|
|
506
|
+
modal.classList.add(`dap-modal-${size}`);
|
|
507
|
+
} else {
|
|
508
|
+
modal.classList.add("dap-modal-medium"); // Default to medium size
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Header
|
|
512
|
+
const header = document.createElement("div");
|
|
513
|
+
header.className = "dap-header-bar";
|
|
514
|
+
|
|
515
|
+
// Close button
|
|
516
|
+
const closeBtn = document.createElement("button");
|
|
517
|
+
closeBtn.className = "dap-modal-close";
|
|
518
|
+
closeBtn.setAttribute("aria-label", "Close modal");
|
|
519
|
+
closeBtn.innerHTML = "×";
|
|
520
|
+
header.appendChild(closeBtn);
|
|
521
|
+
|
|
522
|
+
// Body
|
|
523
|
+
const body = document.createElement("div");
|
|
524
|
+
body.className = "dap-modal-body";
|
|
525
|
+
|
|
526
|
+
// Footer
|
|
527
|
+
const footer = document.createElement("div");
|
|
528
|
+
footer.className = "dap-modal-footer";
|
|
529
|
+
|
|
530
|
+
// Assemble
|
|
531
|
+
modal.appendChild(header);
|
|
532
|
+
modal.appendChild(body);
|
|
533
|
+
modal.appendChild(footer);
|
|
534
|
+
overlay.appendChild(modal);
|
|
535
|
+
|
|
536
|
+
// Accessibility
|
|
537
|
+
modal.setAttribute("role", "dialog");
|
|
538
|
+
modal.setAttribute("aria-modal", "true");
|
|
539
|
+
|
|
540
|
+
return { overlay, modal, header, body, footer };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function renderModalContent(content: ModalContent, step?: any): HTMLElement | null {
|
|
544
|
+
switch (content.kind) {
|
|
545
|
+
case "text":
|
|
546
|
+
const textEl = document.createElement("div");
|
|
547
|
+
textEl.className = "dap-content-text";
|
|
548
|
+
textEl.innerHTML = sanitizeHtml(content.html);
|
|
549
|
+
return textEl;
|
|
550
|
+
|
|
551
|
+
case "link":
|
|
552
|
+
const linkEl = document.createElement("a");
|
|
553
|
+
linkEl.className = "dap-content-link";
|
|
554
|
+
linkEl.href = content.href;
|
|
555
|
+
linkEl.textContent = content.label || content.href;
|
|
556
|
+
linkEl.target = "_blank";
|
|
557
|
+
linkEl.rel = "noopener noreferrer";
|
|
558
|
+
return linkEl;
|
|
559
|
+
|
|
560
|
+
case "image":
|
|
561
|
+
const imgEl = document.createElement("img");
|
|
562
|
+
imgEl.className = "dap-content-image";
|
|
563
|
+
imgEl.src = content.url;
|
|
564
|
+
imgEl.alt = content.alt || "";
|
|
565
|
+
return imgEl;
|
|
566
|
+
|
|
567
|
+
case "video":
|
|
568
|
+
if (content.sources && content.sources.length > 0) {
|
|
569
|
+
const videoEl = document.createElement("video");
|
|
570
|
+
videoEl.className = "dap-content-video";
|
|
571
|
+
videoEl.controls = true;
|
|
572
|
+
|
|
573
|
+
content.sources.forEach(source => {
|
|
574
|
+
const sourceEl = document.createElement("source");
|
|
575
|
+
sourceEl.src = source.src;
|
|
576
|
+
if (source.type) sourceEl.type = source.type;
|
|
577
|
+
videoEl.appendChild(sourceEl);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
return videoEl;
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
|
|
584
|
+
case "youtube":
|
|
585
|
+
const iframeEl = document.createElement("iframe");
|
|
586
|
+
iframeEl.className = "dap-content-youtube";
|
|
587
|
+
iframeEl.src = content.href;
|
|
588
|
+
iframeEl.setAttribute("frameborder", "0");
|
|
589
|
+
iframeEl.setAttribute("allowfullscreen", "true");
|
|
590
|
+
return iframeEl;
|
|
591
|
+
|
|
592
|
+
case "kb":
|
|
593
|
+
// Knowledge base rendering with in-modal viewing
|
|
594
|
+
console.debug("[DAP] Rendering KB content:", content);
|
|
595
|
+
// Store current step and KB data for navigation
|
|
596
|
+
currentSequenceStep = step;
|
|
597
|
+
currentSequenceKBData = content;
|
|
598
|
+
return renderKnowledgeBaseInSequence(content);
|
|
599
|
+
|
|
600
|
+
case "kb-item-viewer":
|
|
601
|
+
// Knowledge base item viewer for modal sequence
|
|
602
|
+
console.debug("[DAP] Rendering KB item viewer:", content);
|
|
603
|
+
return renderKBItemViewerInSequence(content);
|
|
604
|
+
|
|
605
|
+
case "article":
|
|
606
|
+
console.debug("[DAP] Rendering Article in sequence");
|
|
607
|
+
return createArticleViewerInSequence(content);
|
|
608
|
+
|
|
609
|
+
default:
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function waitForElement(selector: string, timeout = 5000): Promise<Element | null> {
|
|
615
|
+
// Check if element already exists
|
|
616
|
+
const existingElement = resolveSelector(selector);
|
|
617
|
+
if (existingElement) return existingElement;
|
|
618
|
+
|
|
619
|
+
// Wait for element to appear
|
|
620
|
+
return new Promise((resolve) => {
|
|
621
|
+
let timeoutId: number;
|
|
622
|
+
let observer: MutationObserver;
|
|
623
|
+
|
|
624
|
+
timeoutId = window.setTimeout(() => {
|
|
625
|
+
observer?.disconnect();
|
|
626
|
+
resolve(null);
|
|
627
|
+
}, timeout);
|
|
628
|
+
|
|
629
|
+
observer = new MutationObserver(() => {
|
|
630
|
+
const element = resolveSelector(selector);
|
|
631
|
+
if (element) {
|
|
632
|
+
clearTimeout(timeoutId);
|
|
633
|
+
observer.disconnect();
|
|
634
|
+
resolve(element);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
observer.observe(document.body, {
|
|
639
|
+
childList: true,
|
|
640
|
+
subtree: true
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function normalizeTrigger(trigger: string): string {
|
|
646
|
+
switch (trigger?.toLowerCase()) {
|
|
647
|
+
case "hover":
|
|
648
|
+
return "mouseenter";
|
|
649
|
+
case "focus":
|
|
650
|
+
return "focus";
|
|
651
|
+
case "click":
|
|
652
|
+
default:
|
|
653
|
+
return "click";
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function ensureStyles() {
|
|
658
|
+
if (!document.getElementById("dap-modal-sequence-style")) {
|
|
659
|
+
const style = document.createElement("style");
|
|
660
|
+
style.id = "dap-modal-sequence-style";
|
|
661
|
+
style.textContent = modalCssText + `
|
|
662
|
+
.dap-modal-nav {
|
|
663
|
+
display: flex;
|
|
664
|
+
justify-content: space-between;
|
|
665
|
+
align-items: center;
|
|
666
|
+
gap: 12px;
|
|
667
|
+
margin-top: 16px;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.dap-step-indicator {
|
|
671
|
+
font-size: 14px;
|
|
672
|
+
color: var(--dap-text-muted);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.dap-btn {
|
|
676
|
+
padding: 8px 16px;
|
|
677
|
+
border: 1px solid var(--dap-border);
|
|
678
|
+
border-radius: 6px;
|
|
679
|
+
font-size: 14px;
|
|
680
|
+
cursor: pointer;
|
|
681
|
+
transition: all 0.15s ease;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.dap-btn-primary {
|
|
685
|
+
background: var(--dap-primary);
|
|
686
|
+
color: white;
|
|
687
|
+
border-color: var(--dap-primary);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.dap-btn-secondary {
|
|
691
|
+
background: transparent;
|
|
692
|
+
color: var(--dap-text);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.dap-btn:disabled {
|
|
696
|
+
opacity: 0.5;
|
|
697
|
+
cursor: not-allowed;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.dap-content-kb {
|
|
701
|
+
margin: 16px 0;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.dap-kb-item {
|
|
705
|
+
margin: 8px 0;
|
|
706
|
+
padding: 8px;
|
|
707
|
+
border: 1px solid var(--dap-border);
|
|
708
|
+
border-radius: 4px;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.dap-kb-item a {
|
|
712
|
+
font-weight: 500;
|
|
713
|
+
color: var(--dap-primary);
|
|
714
|
+
text-decoration: none;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.dap-kb-item p {
|
|
718
|
+
margin: 4px 0 0;
|
|
719
|
+
font-size: 14px;
|
|
720
|
+
color: var(--dap-text-muted);
|
|
721
|
+
}
|
|
722
|
+
`;
|
|
723
|
+
document.head.appendChild(style);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Knowledge Base rendering functions for modal sequences
|
|
728
|
+
function renderKnowledgeBaseInSequence(content: any): HTMLElement {
|
|
729
|
+
const container = document.createElement('div');
|
|
730
|
+
container.className = 'dap-content-kb';
|
|
731
|
+
|
|
732
|
+
console.debug("[DAP] Rendering KB sequence with content:", content);
|
|
733
|
+
console.debug("[DAP] KB items count:", content?.items?.length || 0);
|
|
734
|
+
|
|
735
|
+
if (!content.items || !Array.isArray(content.items)) {
|
|
736
|
+
console.warn("[DAP] No KB items available for rendering");
|
|
737
|
+
container.innerHTML = '<p>No knowledge base items available.</p>';
|
|
738
|
+
return container;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Add title if available
|
|
742
|
+
if (content.title) {
|
|
743
|
+
const title = document.createElement('h3');
|
|
744
|
+
title.textContent = content.title;
|
|
745
|
+
title.className = 'dap-kb-title';
|
|
746
|
+
container.appendChild(title);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Add description if available
|
|
750
|
+
if (content.description) {
|
|
751
|
+
const desc = document.createElement('p');
|
|
752
|
+
desc.textContent = content.description;
|
|
753
|
+
desc.className = 'dap-kb-description';
|
|
754
|
+
container.appendChild(desc);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Create items list
|
|
758
|
+
const itemsList = document.createElement('div');
|
|
759
|
+
itemsList.className = 'dap-kb-items-list';
|
|
760
|
+
|
|
761
|
+
content.items.forEach((item: any) => {
|
|
762
|
+
const itemElement = document.createElement('button');
|
|
763
|
+
itemElement.className = 'dap-kb-item-button';
|
|
764
|
+
itemElement.type = 'button';
|
|
765
|
+
|
|
766
|
+
// Add icon based on content type
|
|
767
|
+
const icon = document.createElement('span');
|
|
768
|
+
icon.className = 'dap-kb-item-icon';
|
|
769
|
+
|
|
770
|
+
const type = item.type?.toLowerCase() || '';
|
|
771
|
+
if (type === 'video' || item.url?.includes('youtube') || item.url?.includes('vimeo')) {
|
|
772
|
+
icon.textContent = '▶';
|
|
773
|
+
} else if (type === 'image' || /\.(jpg|jpeg|png|gif|webp)$/i.test(item.url || '')) {
|
|
774
|
+
icon.textContent = '🖼';
|
|
775
|
+
} else if (type === 'pdf' || /\.pdf$/i.test(item.url || '')) {
|
|
776
|
+
icon.textContent = '📄';
|
|
777
|
+
} else if (type === 'doc' || type === 'docx' || /\.(doc|docx)$/i.test(item.url || '')) {
|
|
778
|
+
icon.textContent = '📝';
|
|
779
|
+
} else {
|
|
780
|
+
icon.textContent = '📰';
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const title = document.createElement('span');
|
|
784
|
+
title.className = 'dap-kb-item-title';
|
|
785
|
+
title.textContent = item.title || 'Untitled Item';
|
|
786
|
+
|
|
787
|
+
const description = document.createElement('span');
|
|
788
|
+
description.className = 'dap-kb-item-description';
|
|
789
|
+
description.textContent = item.description || '';
|
|
790
|
+
|
|
791
|
+
itemElement.appendChild(icon);
|
|
792
|
+
itemElement.appendChild(title);
|
|
793
|
+
if (item.description) {
|
|
794
|
+
itemElement.appendChild(description);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Add click handler to open item in modal
|
|
798
|
+
itemElement.addEventListener('click', () => {
|
|
799
|
+
openKBItemInSequence(item);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
itemsList.appendChild(itemElement);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
container.appendChild(itemsList);
|
|
806
|
+
return container;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function renderKBItemViewerInSequence(content: any): HTMLElement {
|
|
810
|
+
const container = document.createElement('div');
|
|
811
|
+
container.className = 'dap-kb-item-viewer';
|
|
812
|
+
|
|
813
|
+
// Add back button
|
|
814
|
+
const backButton = document.createElement('button');
|
|
815
|
+
backButton.className = 'dap-kb-back-button';
|
|
816
|
+
backButton.innerHTML = '← Back to Knowledge Base';
|
|
817
|
+
backButton.addEventListener('click', goBackToKBListInSequence);
|
|
818
|
+
container.appendChild(backButton);
|
|
819
|
+
|
|
820
|
+
// Add title
|
|
821
|
+
const title = document.createElement('h3');
|
|
822
|
+
title.className = 'dap-kb-item-title';
|
|
823
|
+
title.textContent = content.title || 'Knowledge Base Item';
|
|
824
|
+
container.appendChild(title);
|
|
825
|
+
|
|
826
|
+
// Add content based on type
|
|
827
|
+
const type = content.type?.toLowerCase() || '';
|
|
828
|
+
const url = content.url || '';
|
|
829
|
+
|
|
830
|
+
if (type === 'video' || url.includes('youtube.com') || url.includes('youtu.be') || url.includes('vimeo.com')) {
|
|
831
|
+
const videoContainer = document.createElement('div');
|
|
832
|
+
videoContainer.className = 'dap-kb-video-container';
|
|
833
|
+
|
|
834
|
+
if (url.includes('youtube.com') || url.includes('youtu.be')) {
|
|
835
|
+
// Handle YouTube URLs
|
|
836
|
+
let videoId = '';
|
|
837
|
+
if (url.includes('youtube.com/watch?v=')) {
|
|
838
|
+
videoId = url.split('watch?v=')[1]?.split('&')[0];
|
|
839
|
+
} else if (url.includes('youtu.be/')) {
|
|
840
|
+
videoId = url.split('youtu.be/')[1]?.split('?')[0];
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (videoId) {
|
|
844
|
+
const iframe = document.createElement('iframe');
|
|
845
|
+
iframe.className = 'dap-kb-video';
|
|
846
|
+
iframe.src = `https://www.youtube.com/embed/${videoId}`;
|
|
847
|
+
iframe.setAttribute('frameborder', '0');
|
|
848
|
+
iframe.setAttribute('allowfullscreen', '');
|
|
849
|
+
videoContainer.appendChild(iframe);
|
|
850
|
+
}
|
|
851
|
+
} else if (url.includes('vimeo.com')) {
|
|
852
|
+
// Handle Vimeo URLs
|
|
853
|
+
const videoId = url.split('vimeo.com/')[1]?.split('?')[0];
|
|
854
|
+
if (videoId) {
|
|
855
|
+
const iframe = document.createElement('iframe');
|
|
856
|
+
iframe.className = 'dap-kb-video';
|
|
857
|
+
iframe.src = `https://player.vimeo.com/video/${videoId}`;
|
|
858
|
+
iframe.setAttribute('frameborder', '0');
|
|
859
|
+
iframe.setAttribute('allowfullscreen', '');
|
|
860
|
+
videoContainer.appendChild(iframe);
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
// Direct video file
|
|
864
|
+
const video = document.createElement('video');
|
|
865
|
+
video.className = 'dap-kb-video';
|
|
866
|
+
video.src = url;
|
|
867
|
+
video.controls = true;
|
|
868
|
+
videoContainer.appendChild(video);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
container.appendChild(videoContainer);
|
|
872
|
+
} else if (type === 'image' || /\.(jpg|jpeg|png|gif|webp)$/i.test(url)) {
|
|
873
|
+
const img = document.createElement('img');
|
|
874
|
+
img.className = 'dap-kb-image';
|
|
875
|
+
img.src = url;
|
|
876
|
+
img.alt = content.title || 'Knowledge Base Image';
|
|
877
|
+
container.appendChild(img);
|
|
878
|
+
} else if (type === 'pdf' || /\.pdf$/i.test(url)) {
|
|
879
|
+
const pdfContainer = document.createElement('div');
|
|
880
|
+
pdfContainer.className = 'dap-kb-pdf-container';
|
|
881
|
+
|
|
882
|
+
const iframe = document.createElement('iframe');
|
|
883
|
+
iframe.className = 'dap-kb-pdf-iframe';
|
|
884
|
+
iframe.src = url;
|
|
885
|
+
iframe.setAttribute('frameborder', '0');
|
|
886
|
+
|
|
887
|
+
pdfContainer.appendChild(iframe);
|
|
888
|
+
container.appendChild(pdfContainer);
|
|
889
|
+
} else if (type === 'doc' || type === 'docx' || /\.(doc|docx)$/i.test(url)) {
|
|
890
|
+
const docContainer = document.createElement('div');
|
|
891
|
+
docContainer.className = 'dap-kb-document-container';
|
|
892
|
+
|
|
893
|
+
// For Office documents, we can try Office Online viewer or show download link
|
|
894
|
+
const iframe = document.createElement('iframe');
|
|
895
|
+
iframe.className = 'dap-kb-document-iframe';
|
|
896
|
+
iframe.src = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url)}`;
|
|
897
|
+
iframe.setAttribute('frameborder', '0');
|
|
898
|
+
|
|
899
|
+
docContainer.appendChild(iframe);
|
|
900
|
+
container.appendChild(docContainer);
|
|
901
|
+
} else {
|
|
902
|
+
// Article or other content - use enhanced article viewer
|
|
903
|
+
const mimeType = content.mime || content.mimeType || null;
|
|
904
|
+
|
|
905
|
+
// Determine if this is a document-based article
|
|
906
|
+
const isDocumentArticle = mimeType && (
|
|
907
|
+
mimeType === 'application/pdf' ||
|
|
908
|
+
mimeType.includes('word') ||
|
|
909
|
+
mimeType.includes('msword') ||
|
|
910
|
+
mimeType.includes('presentation') ||
|
|
911
|
+
mimeType.includes('powerpoint') ||
|
|
912
|
+
/\.(pdf|doc|docx|ppt|pptx)$/i.test(url)
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
if (isDocumentArticle) {
|
|
916
|
+
console.debug("[DAP] Rendering document-based article with enhanced viewer");
|
|
917
|
+
// Use the enhanced article viewer for document content
|
|
918
|
+
const articleViewer = createArticleViewerInSequence(content);
|
|
919
|
+
container.appendChild(articleViewer);
|
|
920
|
+
} else {
|
|
921
|
+
// Traditional article content (HTML/text)
|
|
922
|
+
const articleContainer = document.createElement('div');
|
|
923
|
+
articleContainer.className = 'dap-kb-article-container';
|
|
924
|
+
|
|
925
|
+
if (content.content) {
|
|
926
|
+
articleContainer.innerHTML = content.content;
|
|
927
|
+
} else if (url) {
|
|
928
|
+
// Show enhanced fallback for external content
|
|
929
|
+
const fallback = createSequenceFallbackViewer(url, content.fileName || 'External Article',
|
|
930
|
+
'This content is available at an external link.');
|
|
931
|
+
articleContainer.appendChild(fallback);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
container.appendChild(articleContainer);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Add description if available
|
|
939
|
+
if (content.description) {
|
|
940
|
+
const desc = document.createElement('p');
|
|
941
|
+
desc.className = 'dap-kb-item-description';
|
|
942
|
+
desc.textContent = content.description;
|
|
943
|
+
container.appendChild(desc);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
return container;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Global variables to track KB state in sequences
|
|
950
|
+
let currentSequenceKBData: any = null;
|
|
951
|
+
let currentSequenceStep: any = null;
|
|
952
|
+
|
|
953
|
+
function openKBItemInSequence(item: any) {
|
|
954
|
+
console.debug("[DAP] Opening KB item in sequence:", item);
|
|
955
|
+
console.debug("[DAP] KB view changed to item");
|
|
956
|
+
|
|
957
|
+
// Find the current modal and update its content
|
|
958
|
+
const modal = document.querySelector('.dap-modal-content');
|
|
959
|
+
if (modal && currentSequenceStep) {
|
|
960
|
+
console.debug("[DAP] KB items count:", currentSequenceKBData?.items?.length || 0);
|
|
961
|
+
|
|
962
|
+
// Update the step to show item viewer
|
|
963
|
+
const updatedStep = {
|
|
964
|
+
...currentSequenceStep,
|
|
965
|
+
content: {
|
|
966
|
+
type: 'kb-item-viewer',
|
|
967
|
+
...item
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// Re-render the modal content
|
|
972
|
+
const contentElement = modal.querySelector('.dap-content');
|
|
973
|
+
if (contentElement) {
|
|
974
|
+
const newContent = renderKBItemViewerInSequence(item);
|
|
975
|
+
contentElement.innerHTML = '';
|
|
976
|
+
contentElement.appendChild(newContent);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function goBackToKBListInSequence() {
|
|
982
|
+
console.debug("[DAP] Going back to KB list in sequence");
|
|
983
|
+
console.debug("[DAP] KB view changed to list");
|
|
984
|
+
|
|
985
|
+
// Find the current modal and restore KB list view
|
|
986
|
+
const modal = document.querySelector('.dap-modal-content');
|
|
987
|
+
if (modal && currentSequenceKBData && currentSequenceStep) {
|
|
988
|
+
console.debug("[DAP] KB items count:", currentSequenceKBData?.items?.length || 0);
|
|
989
|
+
|
|
990
|
+
// Restore the original KB content
|
|
991
|
+
const updatedStep = {
|
|
992
|
+
...currentSequenceStep,
|
|
993
|
+
content: {
|
|
994
|
+
type: 'kb',
|
|
995
|
+
...currentSequenceKBData
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// Re-render the modal content
|
|
1000
|
+
const contentElement = modal.querySelector('.dap-content');
|
|
1001
|
+
if (contentElement) {
|
|
1002
|
+
const newContent = renderKnowledgeBaseInSequence(currentSequenceKBData);
|
|
1003
|
+
contentElement.innerHTML = '';
|
|
1004
|
+
contentElement.appendChild(newContent);
|
|
1005
|
+
}
|
|
1006
|
+
} else {
|
|
1007
|
+
console.error("[DAP] Cannot go back: missing modal, KB data, or step");
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Article Viewer for Modal Sequences - matches the standard DAP Article behavior
|
|
1012
|
+
function createArticleViewerInSequence(content: any): HTMLElement {
|
|
1013
|
+
console.debug("[DAP] Creating Article viewer in sequence");
|
|
1014
|
+
|
|
1015
|
+
const container = document.createElement("div");
|
|
1016
|
+
container.className = "dap-content-article";
|
|
1017
|
+
|
|
1018
|
+
const url = content.url || content.presignedUrl || "";
|
|
1019
|
+
const fileName = content.fileName || "Document";
|
|
1020
|
+
const title = content.title || fileName;
|
|
1021
|
+
const mimeType = content.mime || content.mimeType || null;
|
|
1022
|
+
|
|
1023
|
+
console.debug("[DAP] Article URL:", url);
|
|
1024
|
+
console.debug("[DAP] Article MIME:", mimeType);
|
|
1025
|
+
|
|
1026
|
+
if (!url) {
|
|
1027
|
+
console.error("[DAP] No URL provided for Article content");
|
|
1028
|
+
container.innerHTML = `
|
|
1029
|
+
<div class="dap-fallback-viewer">
|
|
1030
|
+
<p><strong>No document URL provided</strong></p>
|
|
1031
|
+
<p>Unable to display article content.</p>
|
|
1032
|
+
</div>
|
|
1033
|
+
`;
|
|
1034
|
+
return container;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Determine viewer type based on MIME type and file extension
|
|
1038
|
+
const viewerType = resolveArticleViewerType(url, mimeType, fileName);
|
|
1039
|
+
console.debug("[DAP] Selected viewer type:", viewerType);
|
|
1040
|
+
|
|
1041
|
+
// Add title
|
|
1042
|
+
const titleEl = document.createElement("h4");
|
|
1043
|
+
titleEl.className = "dap-article-title";
|
|
1044
|
+
titleEl.textContent = title;
|
|
1045
|
+
container.appendChild(titleEl);
|
|
1046
|
+
|
|
1047
|
+
// Create appropriate viewer
|
|
1048
|
+
switch (viewerType) {
|
|
1049
|
+
case "pdf":
|
|
1050
|
+
const pdfViewer = createSequencePDFViewer(url, fileName);
|
|
1051
|
+
container.appendChild(pdfViewer);
|
|
1052
|
+
break;
|
|
1053
|
+
|
|
1054
|
+
case "document":
|
|
1055
|
+
const docViewer = createSequenceDocumentViewer(url, fileName, mimeType);
|
|
1056
|
+
container.appendChild(docViewer);
|
|
1057
|
+
break;
|
|
1058
|
+
|
|
1059
|
+
case "presentation":
|
|
1060
|
+
const pptViewer = createSequencePresentationViewer(url, fileName, mimeType);
|
|
1061
|
+
container.appendChild(pptViewer);
|
|
1062
|
+
break;
|
|
1063
|
+
|
|
1064
|
+
case "fallback":
|
|
1065
|
+
default:
|
|
1066
|
+
console.debug("[DAP] Fallback activated for sequence");
|
|
1067
|
+
const fallbackViewer = createSequenceFallbackViewer(url, fileName, "This document cannot be previewed inline.");
|
|
1068
|
+
container.appendChild(fallbackViewer);
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
return container;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Article viewer type resolver for sequences
|
|
1076
|
+
function resolveArticleViewerType(url: string, mimeType: string | null, fileName: string): string {
|
|
1077
|
+
console.debug("[DAP] Resolving Article viewer type");
|
|
1078
|
+
console.debug("[DAP] MIME type:", mimeType || "none");
|
|
1079
|
+
|
|
1080
|
+
// Check MIME type first
|
|
1081
|
+
if (mimeType) {
|
|
1082
|
+
if (mimeType === "application/pdf") {
|
|
1083
|
+
return "pdf";
|
|
1084
|
+
}
|
|
1085
|
+
if (mimeType.includes("word") || mimeType.includes("msword") ||
|
|
1086
|
+
mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
|
|
1087
|
+
return "document";
|
|
1088
|
+
}
|
|
1089
|
+
if (mimeType.includes("presentation") || mimeType.includes("powerpoint") ||
|
|
1090
|
+
mimeType === "application/vnd.openxmlformats-officedocument.presentationml.presentation") {
|
|
1091
|
+
return "presentation";
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Fallback to file extension
|
|
1096
|
+
const urlLower = url.toLowerCase();
|
|
1097
|
+
const fileNameLower = fileName.toLowerCase();
|
|
1098
|
+
|
|
1099
|
+
if (urlLower.includes(".pdf") || fileNameLower.endsWith(".pdf")) {
|
|
1100
|
+
return "pdf";
|
|
1101
|
+
}
|
|
1102
|
+
if (urlLower.match(/\.(doc|docx)/) || fileNameLower.match(/\.(doc|docx)$/)) {
|
|
1103
|
+
return "document";
|
|
1104
|
+
}
|
|
1105
|
+
if (urlLower.match(/\.(ppt|pptx)/) || fileNameLower.match(/\.(ppt|pptx)$/)) {
|
|
1106
|
+
return "presentation";
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return "fallback";
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// PDF viewer for sequences
|
|
1113
|
+
function createSequencePDFViewer(url: string, fileName: string): HTMLElement {
|
|
1114
|
+
const container = document.createElement("div");
|
|
1115
|
+
container.className = "dap-pdf-viewer-container";
|
|
1116
|
+
|
|
1117
|
+
const iframe = document.createElement("iframe");
|
|
1118
|
+
iframe.className = "dap-pdf-iframe";
|
|
1119
|
+
iframe.src = url;
|
|
1120
|
+
iframe.style.width = "100%";
|
|
1121
|
+
iframe.style.height = "400px";
|
|
1122
|
+
iframe.style.border = "1px solid #ddd";
|
|
1123
|
+
iframe.setAttribute("frameborder", "0");
|
|
1124
|
+
|
|
1125
|
+
container.appendChild(iframe);
|
|
1126
|
+
|
|
1127
|
+
// Add action buttons
|
|
1128
|
+
const actions = createSequenceDocumentActions(url, fileName);
|
|
1129
|
+
container.appendChild(actions);
|
|
1130
|
+
|
|
1131
|
+
return container;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Document viewer for sequences
|
|
1135
|
+
function createSequenceDocumentViewer(url: string, fileName: string, mimeType?: string | null): HTMLElement {
|
|
1136
|
+
const container = document.createElement("div");
|
|
1137
|
+
container.className = "dap-document-viewer-container";
|
|
1138
|
+
|
|
1139
|
+
// Try Office Online viewer for supported document types
|
|
1140
|
+
if (mimeType && (mimeType.includes("word") || mimeType.includes("msword"))) {
|
|
1141
|
+
const iframe = document.createElement("iframe");
|
|
1142
|
+
iframe.className = "dap-document-iframe";
|
|
1143
|
+
iframe.src = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url)}`;
|
|
1144
|
+
iframe.style.width = "100%";
|
|
1145
|
+
iframe.style.height = "400px";
|
|
1146
|
+
iframe.style.border = "1px solid #ddd";
|
|
1147
|
+
iframe.setAttribute("frameborder", "0");
|
|
1148
|
+
|
|
1149
|
+
container.appendChild(iframe);
|
|
1150
|
+
} else {
|
|
1151
|
+
// Show fallback for unsupported document types
|
|
1152
|
+
const fallback = createSequenceFallbackViewer(url, fileName, "Document preview is not supported for this file type.");
|
|
1153
|
+
container.appendChild(fallback);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Add action buttons
|
|
1157
|
+
const actions = createSequenceDocumentActions(url, fileName);
|
|
1158
|
+
container.appendChild(actions);
|
|
1159
|
+
|
|
1160
|
+
return container;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Presentation viewer for sequences
|
|
1164
|
+
function createSequencePresentationViewer(url: string, fileName: string, mimeType?: string | null): HTMLElement {
|
|
1165
|
+
const container = document.createElement("div");
|
|
1166
|
+
container.className = "dap-presentation-viewer-container";
|
|
1167
|
+
|
|
1168
|
+
// Try Office Online viewer for presentations
|
|
1169
|
+
if (mimeType && mimeType.includes("presentation")) {
|
|
1170
|
+
const iframe = document.createElement("iframe");
|
|
1171
|
+
iframe.className = "dap-presentation-iframe";
|
|
1172
|
+
iframe.src = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url)}`;
|
|
1173
|
+
iframe.style.width = "100%";
|
|
1174
|
+
iframe.style.height = "400px";
|
|
1175
|
+
iframe.style.border = "1px solid #ddd";
|
|
1176
|
+
iframe.setAttribute("frameborder", "0");
|
|
1177
|
+
|
|
1178
|
+
container.appendChild(iframe);
|
|
1179
|
+
} else {
|
|
1180
|
+
// Show fallback for unsupported presentation types
|
|
1181
|
+
const fallback = createSequenceFallbackViewer(url, fileName, "Presentation preview is not supported for this file type.");
|
|
1182
|
+
container.appendChild(fallback);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Add action buttons
|
|
1186
|
+
const actions = createSequenceDocumentActions(url, fileName);
|
|
1187
|
+
container.appendChild(actions);
|
|
1188
|
+
|
|
1189
|
+
return container;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Fallback viewer for sequences
|
|
1193
|
+
function createSequenceFallbackViewer(url: string, fileName: string, message: string): HTMLElement {
|
|
1194
|
+
const container = document.createElement("div");
|
|
1195
|
+
container.className = "dap-fallback-viewer";
|
|
1196
|
+
|
|
1197
|
+
container.innerHTML = `
|
|
1198
|
+
<div class="dap-fallback-message">
|
|
1199
|
+
<p><strong>${message}</strong></p>
|
|
1200
|
+
<p>File: ${fileName}</p>
|
|
1201
|
+
</div>
|
|
1202
|
+
`;
|
|
1203
|
+
|
|
1204
|
+
// Add action buttons
|
|
1205
|
+
const actions = createSequenceDocumentActions(url, fileName);
|
|
1206
|
+
container.appendChild(actions);
|
|
1207
|
+
|
|
1208
|
+
return container;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Document action buttons for sequences
|
|
1212
|
+
function createSequenceDocumentActions(url: string, fileName: string): HTMLElement {
|
|
1213
|
+
const actions = document.createElement("div");
|
|
1214
|
+
actions.className = "dap-document-actions";
|
|
1215
|
+
|
|
1216
|
+
// Download button
|
|
1217
|
+
const downloadBtn = document.createElement("button");
|
|
1218
|
+
downloadBtn.className = "dap-action-btn dap-download-btn";
|
|
1219
|
+
downloadBtn.textContent = "Download";
|
|
1220
|
+
downloadBtn.addEventListener("click", (e) => {
|
|
1221
|
+
e.preventDefault();
|
|
1222
|
+
e.stopPropagation();
|
|
1223
|
+
|
|
1224
|
+
const link = document.createElement("a");
|
|
1225
|
+
link.href = url;
|
|
1226
|
+
link.download = fileName || "download";
|
|
1227
|
+
link.target = "_blank";
|
|
1228
|
+
document.body.appendChild(link);
|
|
1229
|
+
link.click();
|
|
1230
|
+
document.body.removeChild(link);
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
// Open in new tab button
|
|
1234
|
+
const openBtn = document.createElement("button");
|
|
1235
|
+
openBtn.className = "dap-action-btn dap-open-btn";
|
|
1236
|
+
openBtn.textContent = "Open in New Tab";
|
|
1237
|
+
openBtn.addEventListener("click", (e) => {
|
|
1238
|
+
e.preventDefault();
|
|
1239
|
+
e.stopPropagation();
|
|
1240
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
actions.appendChild(downloadBtn);
|
|
1244
|
+
actions.appendChild(openBtn);
|
|
1245
|
+
|
|
1246
|
+
return actions;
|
|
1247
|
+
}
|