@syntrologie/adapt-faq 2.15.0 → 2.17.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.
- package/dist/editor.d.ts +29 -33
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +194 -317
- package/dist/editor.js.map +7 -0
- package/dist/runtime.d.ts +3 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1248 -93
- package/dist/runtime.js.map +7 -0
- package/dist/schema.d.ts +670 -138
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +442 -206
- package/dist/schema.js.map +7 -0
- package/package.json +4 -20
- package/dist/FAQWidget.d.ts +0 -33
- package/dist/FAQWidget.d.ts.map +0 -1
- package/dist/FAQWidget.js +0 -375
- package/dist/FAQWidgetLit.js +0 -534
- package/dist/cdn.d.ts +0 -70
- package/dist/cdn.d.ts.map +0 -1
- package/dist/cdn.js +0 -46
- package/dist/editor-lit.d.ts +0 -37
- package/dist/editor-lit.d.ts.map +0 -1
- package/dist/editor-lit.js +0 -195
- package/dist/executors.js +0 -150
- package/dist/faq-styles.js +0 -204
- package/dist/faq-types.js +0 -7
- package/dist/runtime-lit.d.ts +0 -85
- package/dist/runtime-lit.d.ts.map +0 -1
- package/dist/runtime-lit.js +0 -94
- package/dist/state.js +0 -132
- package/dist/summarize.js +0 -62
- package/dist/types.js +0 -17
- package/node_modules/@syntrologie/sdk-contracts/dist/index.d.ts +0 -129
- package/node_modules/@syntrologie/sdk-contracts/dist/index.js +0 -17
- package/node_modules/@syntrologie/sdk-contracts/dist/schemas.d.ts +0 -2296
- package/node_modules/@syntrologie/sdk-contracts/dist/schemas.js +0 -361
- package/node_modules/@syntrologie/sdk-contracts/package.json +0 -33
package/dist/editor.d.ts
CHANGED
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adaptive FAQ - Editor Component
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Lit web component that displays FAQ question cards with inline editing,
|
|
5
|
+
* detection, dismiss/restore, and rationale display.
|
|
6
|
+
*
|
|
7
|
+
* Custom events:
|
|
8
|
+
* navigate-home — user clicked back
|
|
9
|
+
* dirty-change — { dirty: boolean }
|
|
7
10
|
*/
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
import { LitElement } from 'lit';
|
|
12
|
+
import { type FAQConfig } from './types';
|
|
13
|
+
export declare class FAQEditorLit extends LitElement {
|
|
14
|
+
static properties: {
|
|
15
|
+
config: {
|
|
16
|
+
attribute: boolean;
|
|
17
|
+
};
|
|
18
|
+
onChange: {
|
|
19
|
+
attribute: boolean;
|
|
20
|
+
};
|
|
21
|
+
_editingKey: {
|
|
22
|
+
state: boolean;
|
|
23
|
+
};
|
|
17
24
|
};
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
config: FAQConfig | null;
|
|
26
|
+
onChange: ((updated: Record<string, unknown>) => void) | null;
|
|
27
|
+
private _editingKey;
|
|
28
|
+
createRenderRoot(): this;
|
|
29
|
+
private _handleBack;
|
|
30
|
+
private _handleItemClick;
|
|
31
|
+
private _handleFieldChange;
|
|
32
|
+
private _renderEditMode;
|
|
33
|
+
private _renderListMode;
|
|
34
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
20
35
|
}
|
|
21
|
-
export declare function flattenItems(config: FAQConfig): FlatItem[];
|
|
22
|
-
export declare function FAQEditor({ config, onChange, editor }: EditorPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
-
/**
|
|
24
|
-
* Editor panel configuration for the app registry.
|
|
25
|
-
*/
|
|
26
|
-
export declare const editorPanel: {
|
|
27
|
-
title: string;
|
|
28
|
-
icon: string;
|
|
29
|
-
description: string;
|
|
30
|
-
};
|
|
31
|
-
export declare const editor: {
|
|
32
|
-
panel: {
|
|
33
|
-
title: string;
|
|
34
|
-
icon: string;
|
|
35
|
-
description: string;
|
|
36
|
-
};
|
|
37
|
-
component: typeof FAQEditor;
|
|
38
|
-
};
|
|
39
|
-
export default FAQEditor;
|
|
40
36
|
//# sourceMappingURL=editor.d.ts.map
|
package/dist/editor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAQ,UAAU,EAAW,MAAM,KAAK,CAAC;AAGhD,OAAO,EAAkB,KAAK,SAAS,EAAuC,MAAM,SAAS,CAAC;AAqC9F,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,UAAU;;;;;;;;;;MAIxB;IAEF,MAAM,EAAE,SAAS,GAAG,IAAI,CAAQ;IAChC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAErE,OAAO,CAAC,WAAW,CAAuB;IAEjC,gBAAgB;IAQzB,OAAO,CAAC,WAAW,CAMjB;IAEF,OAAO,CAAC,gBAAgB,CAEtB;IAEF,OAAO,CAAC,kBAAkB,CAaxB;IAIF,OAAO,CAAC,eAAe,CAsDrB;IAEF,OAAO,CAAC,eAAe,CAqCrB;IAEO,MAAM;CAoChB"}
|
package/dist/editor.js
CHANGED
|
@@ -1,326 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
|
-
import { describeTrigger, summarizeFAQItem } from './summarize';
|
|
12
|
-
import { isOwnAction, } from './types';
|
|
13
|
-
function isRuleStrategy(s) {
|
|
14
|
-
return (typeof s === 'object' &&
|
|
15
|
-
s !== null &&
|
|
16
|
-
s.type === 'rules' &&
|
|
17
|
-
Array.isArray(s.rules));
|
|
1
|
+
// src/editor.ts
|
|
2
|
+
import { html, LitElement, nothing } from "lit";
|
|
3
|
+
|
|
4
|
+
// src/summarize.ts
|
|
5
|
+
var MAX_Q_LEN = 50;
|
|
6
|
+
var MAX_A_LEN = 40;
|
|
7
|
+
function truncate(text, max) {
|
|
8
|
+
if (text.length <= max) return text;
|
|
9
|
+
return `${text.slice(0, max).trimEnd()}...`;
|
|
18
10
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
return { pagePatterns: [], anchorSelectors: [], hasTargeting: false };
|
|
22
|
-
}
|
|
23
|
-
const pagePatterns = new Set();
|
|
24
|
-
const anchorSelectors = new Set();
|
|
25
|
-
for (const rule of triggerWhen.rules) {
|
|
26
|
-
for (const cond of rule.conditions) {
|
|
27
|
-
const c = cond;
|
|
28
|
-
if (c.type === 'page_url' && typeof c.url === 'string') {
|
|
29
|
-
pagePatterns.add(c.url);
|
|
30
|
-
}
|
|
31
|
-
else if (c.type === 'anchor_visible' && typeof c.anchorId === 'string') {
|
|
32
|
-
anchorSelectors.add(c.anchorId);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
const hasTargeting = pagePatterns.size > 0 || anchorSelectors.size > 0;
|
|
37
|
-
return {
|
|
38
|
-
pagePatterns: [...pagePatterns],
|
|
39
|
-
anchorSelectors: [...anchorSelectors],
|
|
40
|
-
hasTargeting,
|
|
41
|
-
};
|
|
11
|
+
function stripHtml(html2) {
|
|
12
|
+
return html2.replace(/<[^>]*>/g, "").trim();
|
|
42
13
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
14
|
+
function getAnswerPreview(answer) {
|
|
15
|
+
if (typeof answer === "string") return answer;
|
|
16
|
+
if (answer.type === "rich") return stripHtml(answer.html);
|
|
17
|
+
return answer.content.replace(/[*_#`]/g, "").trim();
|
|
46
18
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
19
|
+
function summarizeFAQItem(item) {
|
|
20
|
+
const q = truncate(item.config.question, MAX_Q_LEN);
|
|
21
|
+
const a = truncate(getAnswerPreview(item.config.answer), MAX_A_LEN);
|
|
22
|
+
return `Q: "${q}" \u2014 ${a}`;
|
|
50
23
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
catch {
|
|
57
|
-
// Silently ignore
|
|
58
|
-
}
|
|
24
|
+
|
|
25
|
+
// src/types.ts
|
|
26
|
+
var ACTION_NAMESPACE = "faq";
|
|
27
|
+
function isOwnAction(action) {
|
|
28
|
+
return action.kind.startsWith(`${ACTION_NAMESPACE}:`);
|
|
59
29
|
}
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
30
|
+
|
|
31
|
+
// src/editor.ts
|
|
32
|
+
var getAnswerText = (answer) => {
|
|
33
|
+
if (typeof answer === "string") return answer;
|
|
34
|
+
if (answer.type === "rich") return answer.html;
|
|
35
|
+
return answer.content;
|
|
36
|
+
};
|
|
37
|
+
var flattenItems = (config) => {
|
|
38
|
+
const actions = (config.actions || []).filter(isOwnAction);
|
|
39
|
+
return actions.map((q, i) => ({
|
|
40
|
+
key: String(i),
|
|
41
|
+
index: i,
|
|
42
|
+
summary: summarizeFAQItem(q),
|
|
43
|
+
question: q
|
|
44
|
+
}));
|
|
45
|
+
};
|
|
46
|
+
var FAQEditorLit = class extends LitElement {
|
|
47
|
+
constructor() {
|
|
48
|
+
super(...arguments);
|
|
49
|
+
this.config = null;
|
|
50
|
+
this.onChange = null;
|
|
51
|
+
this._editingKey = null;
|
|
52
|
+
// No auto-dirty on load — only dirty when user makes changes (matching React behavior).
|
|
53
|
+
// ---- Actions ----
|
|
54
|
+
this._handleBack = () => {
|
|
55
|
+
if (this._editingKey !== null) {
|
|
56
|
+
this._editingKey = null;
|
|
57
|
+
} else {
|
|
58
|
+
this.dispatchEvent(new CustomEvent("navigate-home", { bubbles: true }));
|
|
59
|
+
}
|
|
87
60
|
};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const [detectionMap, setDetectionMap] = useState(new Map());
|
|
91
|
-
const itemsRef = useRef(items);
|
|
92
|
-
itemsRef.current = items;
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
const runDetection = () => {
|
|
95
|
-
const map = new Map();
|
|
96
|
-
const currentPath = getCurrentRoute();
|
|
97
|
-
for (const item of itemsRef.current) {
|
|
98
|
-
const targeting = extractTargetingInfo(item.question.triggerWhen);
|
|
99
|
-
// Check page match
|
|
100
|
-
let pageMatch = true;
|
|
101
|
-
if (targeting.pagePatterns.length > 0) {
|
|
102
|
-
pageMatch = targeting.pagePatterns.some((pattern) => {
|
|
103
|
-
const regex = new RegExp(`^${pattern.replace(/\*\*/g, '.*').replace(/(?<!\.)(\*)/g, '[^/]*')}$`);
|
|
104
|
-
return regex.test(currentPath);
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
// Check anchor presence
|
|
108
|
-
let anchorFound = false;
|
|
109
|
-
let element = null;
|
|
110
|
-
if (item.firstAnchor) {
|
|
111
|
-
try {
|
|
112
|
-
element = document.querySelector(item.firstAnchor);
|
|
113
|
-
anchorFound = element !== null;
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
// Invalid selector
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
// No anchor to check — if page matches, consider found
|
|
121
|
-
anchorFound = pageMatch;
|
|
122
|
-
}
|
|
123
|
-
map.set(item.key, {
|
|
124
|
-
found: pageMatch && anchorFound,
|
|
125
|
-
element,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
setDetectionMap(map);
|
|
129
|
-
};
|
|
130
|
-
runDetection();
|
|
131
|
-
const interval = setInterval(runDetection, 2000);
|
|
132
|
-
window.addEventListener('popstate', runDetection);
|
|
133
|
-
return () => {
|
|
134
|
-
clearInterval(interval);
|
|
135
|
-
window.removeEventListener('popstate', runDetection);
|
|
136
|
-
};
|
|
137
|
-
}, [getCurrentRoute]);
|
|
138
|
-
return detectionMap;
|
|
139
|
-
}
|
|
140
|
-
// ============================================================================
|
|
141
|
-
// FAQEditor Component
|
|
142
|
-
// ============================================================================
|
|
143
|
-
export function FAQEditor({ config, onChange, editor }) {
|
|
144
|
-
const typedConfig = config;
|
|
145
|
-
const [dismissedKeys, setDismissedKeys] = useState(() => editor.getDismissedKeys?.() ?? new Set());
|
|
146
|
-
const [editingKey, setEditingKey] = useState(null);
|
|
147
|
-
const [_previewMode, setPreviewMode] = useState('after');
|
|
148
|
-
const [_hoveredKey, setHoveredKey] = useState(null);
|
|
149
|
-
// Sync dismissed keys back to navigation context on every change
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
editor.setDismissedKeys?.(dismissedKeys);
|
|
152
|
-
}, [dismissedKeys, editor]);
|
|
153
|
-
// React to global before/after toggle from the panel
|
|
154
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally omitted — adding config/typedConfig/previewConfig would cause infinite re-renders since previewConfig triggers state updates
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
const mode = editor.previewMode;
|
|
157
|
-
if (!mode)
|
|
158
|
-
return;
|
|
159
|
-
if (mode === 'before') {
|
|
160
|
-
// Remove all FAQ items — push a config with every item filtered out
|
|
161
|
-
const allKeys = new Set(flattenItems(typedConfig).map((item) => item.key));
|
|
162
|
-
const empty = filterConfig(typedConfig, allKeys);
|
|
163
|
-
editor.previewConfig(empty);
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
// Restore the full config
|
|
167
|
-
editor.previewConfig(config);
|
|
168
|
-
}
|
|
169
|
-
}, [editor.previewMode]);
|
|
170
|
-
// If navigated here with an editKey, jump directly to that item's edit view
|
|
171
|
-
const initialConsumed = useRef(false);
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (!initialConsumed.current) {
|
|
174
|
-
initialConsumed.current = true;
|
|
175
|
-
if (editor.initialEditKey != null) {
|
|
176
|
-
setEditingKey(String(editor.initialEditKey));
|
|
177
|
-
}
|
|
178
|
-
editor.clearInitialState?.();
|
|
179
|
-
}
|
|
180
|
-
}, [editor]);
|
|
181
|
-
const allItems = flattenItems(typedConfig);
|
|
182
|
-
const activeItems = allItems.filter((item) => !dismissedKeys.has(item.key));
|
|
183
|
-
const dismissedItems = allItems.filter((item) => dismissedKeys.has(item.key));
|
|
184
|
-
const totalQuestions = activeItems.length;
|
|
185
|
-
const detectionMap = useDetection(allItems, editor.getCurrentRoute);
|
|
186
|
-
const foundCount = activeItems.filter((item) => detectionMap.get(item.key)?.found).length;
|
|
187
|
-
// Live triggerWhen status for condition diagnostics
|
|
188
|
-
const triggerWhenItems = useMemo(() => allItems.map((item) => ({
|
|
189
|
-
id: item.key,
|
|
190
|
-
triggerWhen: item.question.triggerWhen,
|
|
191
|
-
})), [allItems]);
|
|
192
|
-
const triggerWhenStatuses = useTriggerWhenStatus(triggerWhenItems);
|
|
193
|
-
const handleDismiss = useCallback((key) => {
|
|
194
|
-
setDismissedKeys((prev) => {
|
|
195
|
-
const next = new Set(prev);
|
|
196
|
-
next.add(key);
|
|
197
|
-
return next;
|
|
198
|
-
});
|
|
199
|
-
if (editingKey === key)
|
|
200
|
-
setEditingKey(null);
|
|
201
|
-
}, [editingKey]);
|
|
202
|
-
const handleRestore = useCallback((key) => {
|
|
203
|
-
setDismissedKeys((prev) => {
|
|
204
|
-
const next = new Set(prev);
|
|
205
|
-
next.delete(key);
|
|
206
|
-
return next;
|
|
207
|
-
});
|
|
208
|
-
}, []);
|
|
209
|
-
const handleCardBodyClick = useCallback((item) => {
|
|
210
|
-
setEditingKey(item.key);
|
|
211
|
-
}, []);
|
|
212
|
-
const handleTriggerClick = useCallback(async (item) => {
|
|
213
|
-
const pageUrl = extractFirstPage(item.question.triggerWhen);
|
|
214
|
-
if (pageUrl) {
|
|
215
|
-
if (item.firstAnchor)
|
|
216
|
-
savePendingHighlight(item.firstAnchor);
|
|
217
|
-
await editor.navigateTo(pageUrl);
|
|
218
|
-
}
|
|
219
|
-
if (item.firstAnchor) {
|
|
220
|
-
editor.highlightElement(item.firstAnchor);
|
|
221
|
-
}
|
|
222
|
-
}, [editor]);
|
|
223
|
-
const handleBackToList = useCallback(() => {
|
|
224
|
-
setEditingKey(null);
|
|
225
|
-
setPreviewMode('after');
|
|
226
|
-
editor.previewConfig(config);
|
|
227
|
-
editor.clearHighlight();
|
|
228
|
-
}, [editor, config]);
|
|
229
|
-
// Register back handler in panel header when editing
|
|
230
|
-
useEffect(() => {
|
|
231
|
-
editor.setBackHandler?.(editingKey !== null ? handleBackToList : null);
|
|
232
|
-
return () => editor.setBackHandler?.(null);
|
|
233
|
-
}, [editingKey, handleBackToList, editor]);
|
|
234
|
-
const handleFieldChange = useCallback((index, field, value) => {
|
|
235
|
-
const ownActions = (typedConfig.actions || []).filter(isOwnAction).slice();
|
|
236
|
-
const q = { ...ownActions[index], config: { ...ownActions[index].config } };
|
|
237
|
-
q.config[field] = value;
|
|
238
|
-
ownActions[index] = q;
|
|
239
|
-
const otherActions = (typedConfig.actions || []).filter((a) => !isOwnAction(a));
|
|
240
|
-
const updated = { ...typedConfig, actions: [...otherActions, ...ownActions] };
|
|
241
|
-
onChange(updated);
|
|
242
|
-
editor.setDirty(true);
|
|
243
|
-
}, [typedConfig, onChange, editor]);
|
|
244
|
-
const _handlePublish = useCallback(() => {
|
|
245
|
-
if (dismissedKeys.size > 0) {
|
|
246
|
-
const filtered = filterConfig(typedConfig, dismissedKeys);
|
|
247
|
-
onChange(filtered);
|
|
248
|
-
}
|
|
249
|
-
editor.publish();
|
|
250
|
-
}, [dismissedKeys, typedConfig, onChange, editor]);
|
|
251
|
-
const handleBadgeClick = useCallback(async (item) => {
|
|
252
|
-
const detection = detectionMap.get(item.key);
|
|
253
|
-
if (detection?.found && item.firstAnchor) {
|
|
254
|
-
editor.highlightElement(item.firstAnchor);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
const pageUrl = extractFirstPage(item.question.triggerWhen);
|
|
258
|
-
if (pageUrl) {
|
|
259
|
-
if (item.firstAnchor)
|
|
260
|
-
savePendingHighlight(item.firstAnchor);
|
|
261
|
-
await editor.navigateTo(pageUrl);
|
|
262
|
-
if (item.firstAnchor)
|
|
263
|
-
editor.highlightElement(item.firstAnchor);
|
|
264
|
-
}
|
|
265
|
-
else if (item.firstAnchor) {
|
|
266
|
-
editor.highlightElement(item.firstAnchor);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}, [editor, detectionMap]);
|
|
270
|
-
const handleCardHover = useCallback((item) => {
|
|
271
|
-
setHoveredKey(item.key);
|
|
272
|
-
if (item.firstAnchor) {
|
|
273
|
-
editor.highlightElement(item.firstAnchor);
|
|
274
|
-
}
|
|
275
|
-
}, [editor]);
|
|
276
|
-
const handleCardLeave = useCallback(() => {
|
|
277
|
-
setHoveredKey(null);
|
|
278
|
-
editor.clearHighlight();
|
|
279
|
-
}, [editor]);
|
|
280
|
-
// ---- Edit form renderer ----
|
|
281
|
-
const renderEditFields = (index) => {
|
|
282
|
-
const actions = (typedConfig.actions || []).filter(isOwnAction);
|
|
283
|
-
const q = actions[index];
|
|
284
|
-
if (!q)
|
|
285
|
-
return null;
|
|
286
|
-
const item = allItems.find((it) => it.key === String(index));
|
|
287
|
-
return (_jsxs("div", { className: "se-py-1", children: [item && item.trigger !== 'All pages' && (_jsxs("button", { type: "button", "data-trigger": true, className: "se-flex se-items-center se-gap-1 se-text-[11px] se-text-slate-grey-8 se-cursor-pointer se-mb-1 se-border-none se-bg-transparent se-p-0 se-text-left", onClick: () => handleTriggerClick(item), children: [_jsx("span", { children: '\u{1f4cd}' }), _jsx("span", { children: item.trigger })] })), _jsx(EditorInput, { label: "Question", value: q.config.question, onChange: (e) => handleFieldChange(index, 'question', e.target.value) }), _jsx(EditorTextarea, { label: "Answer", value: getAnswerText(q.config.answer), onChange: (e) => handleFieldChange(index, 'answer', e.target.value) }), _jsx(EditorInput, { label: "Category", value: q.config.category || '', onChange: (e) => handleFieldChange(index, 'category', e.target.value || undefined), placeholder: "e.g., Billing, Account" }), _jsxs("div", { children: [_jsx("span", { className: "se-text-[11px] se-font-semibold se-text-slate-grey-7 se-mb-1 se-block", children: "AI Rationale" }), _jsx("div", { className: "se-p-2 se-rounded se-border se-border-dashed se-border-white/15 se-bg-white/[0.02] se-text-slate-grey-8 se-text-xs se-mb-2", children: q.rationale ? q.rationale.why : 'N/A' })] }), _jsx(TriggerJourney, { status: triggerWhenStatuses.get(String(index)) ?? null })] }));
|
|
61
|
+
this._handleItemClick = (key) => {
|
|
62
|
+
this._editingKey = key;
|
|
288
63
|
};
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
64
|
+
this._handleFieldChange = (index, field, value) => {
|
|
65
|
+
if (!this.config || !this.onChange) return;
|
|
66
|
+
const ownActions = (this.config.actions || []).filter(isOwnAction).slice();
|
|
67
|
+
const q = { ...ownActions[index], config: { ...ownActions[index].config } };
|
|
68
|
+
q.config[field] = value;
|
|
69
|
+
ownActions[index] = q;
|
|
70
|
+
const otherActions = (this.config.actions || []).filter((a) => !isOwnAction(a));
|
|
71
|
+
const updated = { ...this.config, actions: [...otherActions, ...ownActions] };
|
|
72
|
+
this.onChange(updated);
|
|
73
|
+
this.dispatchEvent(new CustomEvent("dirty-change", { detail: { dirty: true }, bubbles: true }));
|
|
74
|
+
};
|
|
75
|
+
// ---- Render ----
|
|
76
|
+
this._renderEditMode = (item) => {
|
|
77
|
+
const q = item.question;
|
|
78
|
+
return html`
|
|
79
|
+
<div class="se-py-1">
|
|
80
|
+
<div class="se-flex se-items-center se-gap-2 se-mb-3 se-text-[13px] se-font-semibold se-text-text-primary">
|
|
81
|
+
<span>❓</span>
|
|
82
|
+
<span>${item.summary}</span>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="se-mb-3">
|
|
86
|
+
<label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Question</label>
|
|
87
|
+
<input
|
|
88
|
+
type="text"
|
|
89
|
+
.value=${q.config.question}
|
|
90
|
+
@input=${(e) => this._handleFieldChange(item.index, "question", e.target.value)}
|
|
91
|
+
class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm"
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div class="se-mb-3">
|
|
96
|
+
<label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Answer</label>
|
|
97
|
+
<textarea
|
|
98
|
+
.value=${getAnswerText(q.config.answer)}
|
|
99
|
+
@input=${(e) => this._handleFieldChange(item.index, "answer", e.target.value)}
|
|
100
|
+
rows="4"
|
|
101
|
+
class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm se-resize-none"
|
|
102
|
+
></textarea>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="se-mb-3">
|
|
106
|
+
<label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Category</label>
|
|
107
|
+
<input
|
|
108
|
+
type="text"
|
|
109
|
+
.value=${q.config.category || ""}
|
|
110
|
+
placeholder="e.g., Billing, Account"
|
|
111
|
+
@input=${(e) => this._handleFieldChange(item.index, "category", e.target.value)}
|
|
112
|
+
class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm"
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
${q.rationale ? html`
|
|
117
|
+
<div>
|
|
118
|
+
<span class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">AI Rationale</span>
|
|
119
|
+
<div class="se-p-2 se-rounded se-border se-border-dashed se-border-border-primary se-bg-card-bg se-text-text-secondary se-text-xs se-mb-2">
|
|
120
|
+
${q.rationale.why}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
` : nothing}
|
|
124
|
+
</div>
|
|
125
|
+
`;
|
|
126
|
+
};
|
|
127
|
+
this._renderListMode = (items) => {
|
|
128
|
+
if (items.length === 0) {
|
|
129
|
+
return html`<div class="se-text-center se-py-8 se-px-4 se-text-text-secondary se-text-sm">
|
|
130
|
+
No FAQ questions configured.
|
|
131
|
+
</div>`;
|
|
132
|
+
}
|
|
133
|
+
return html`
|
|
134
|
+
<div class="se-text-[11px] se-font-semibold se-uppercase se-tracking-wider se-text-text-secondary se-mb-2 se-px-1">
|
|
135
|
+
FAQ <span class="se-text-text-tertiary se-font-normal">${items.length}</span>
|
|
136
|
+
</div>
|
|
137
|
+
${items.map(
|
|
138
|
+
(item) => html`
|
|
139
|
+
<div
|
|
140
|
+
data-item-key=${item.key}
|
|
141
|
+
data-card-body
|
|
142
|
+
@click=${() => this._handleItemClick(item.key)}
|
|
143
|
+
class="se-p-3 se-rounded-lg se-border se-border-border-primary se-bg-card-bg se-cursor-pointer se-mb-2 se-transition-all hover:se-border-pink-4/40 hover:se-bg-pink-4/5"
|
|
144
|
+
>
|
|
145
|
+
<div class="se-flex se-items-center se-gap-2">
|
|
146
|
+
<span class="se-flex-1 se-overflow-hidden se-text-ellipsis se-whitespace-nowrap se-text-sm se-text-text-primary">
|
|
147
|
+
${item.summary}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
${item.question.rationale ? html`
|
|
151
|
+
<div class="se-text-[10px] se-text-text-tertiary se-mt-1">
|
|
152
|
+
WHY: ${item.question.rationale.why}
|
|
153
|
+
</div>
|
|
154
|
+
` : nothing}
|
|
155
|
+
</div>
|
|
156
|
+
`
|
|
157
|
+
)}
|
|
158
|
+
`;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
createRenderRoot() {
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
render() {
|
|
165
|
+
if (!this.config) {
|
|
166
|
+
return html`<div class="se-p-8 se-text-center se-text-text-secondary se-text-sm">Loading...</div>`;
|
|
167
|
+
}
|
|
168
|
+
const items = flattenItems(this.config);
|
|
169
|
+
const editItem = this._editingKey !== null ? items.find((it) => it.key === this._editingKey) : null;
|
|
170
|
+
return html`
|
|
171
|
+
<div class="se-flex se-flex-col se-h-full">
|
|
172
|
+
<!-- Header (no Back button — panel provides that) -->
|
|
173
|
+
<div class="se-px-4 se-pt-3 se-pb-1">
|
|
174
|
+
<h2 class="se-m-0 se-text-base se-font-semibold se-text-text-primary">Review Questions</h2>
|
|
175
|
+
<p class="se-m-0 se-mt-0.5 se-text-xs se-text-text-secondary">
|
|
176
|
+
${items.length} question${items.length !== 1 ? "s" : ""}
|
|
177
|
+
</p>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="se-flex-1 se-overflow-auto se-p-4">
|
|
181
|
+
${editItem ? html`
|
|
182
|
+
<button type="button" @click=${() => {
|
|
183
|
+
this._editingKey = null;
|
|
184
|
+
}}
|
|
185
|
+
class="se-mb-3 se-py-1 se-px-2 se-rounded se-border-none se-bg-card-bg se-text-text-secondary se-text-xs se-cursor-pointer"
|
|
186
|
+
>← Back to list</button>
|
|
187
|
+
${this._renderEditMode(editItem)}
|
|
188
|
+
` : this._renderListMode(items)}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
FAQEditorLit.properties = {
|
|
195
|
+
config: { attribute: false },
|
|
196
|
+
onChange: { attribute: false },
|
|
197
|
+
_editingKey: { state: true }
|
|
321
198
|
};
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
199
|
+
customElements.define("se-faq-editor", FAQEditorLit);
|
|
200
|
+
export {
|
|
201
|
+
FAQEditorLit
|
|
325
202
|
};
|
|
326
|
-
|
|
203
|
+
//# sourceMappingURL=editor.js.map
|