vibespot 0.4.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/LICENSE +33 -0
- package/README.md +118 -0
- package/assets/content-guide.md +445 -0
- package/assets/conversion-guide.md +693 -0
- package/assets/design-guide.md +380 -0
- package/assets/hubspot-rules.md +560 -0
- package/assets/page-types.md +116 -0
- package/bin/vibespot.mjs +11 -0
- package/dist/index.js +6552 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/ui/chat.js +803 -0
- package/ui/dashboard.js +383 -0
- package/ui/dialog.js +117 -0
- package/ui/field-editor.js +292 -0
- package/ui/index.html +393 -0
- package/ui/preview.js +132 -0
- package/ui/settings.js +927 -0
- package/ui/setup.js +830 -0
- package/ui/styles.css +2552 -0
- package/ui/upload-panel.js +554 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field editor sidebar — edit module field values with live preview.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const editorEl = document.getElementById("field-editor");
|
|
6
|
+
const editorTitle = document.getElementById("field-editor-title");
|
|
7
|
+
const editorContent = document.getElementById("field-editor-content");
|
|
8
|
+
const editorClose = document.getElementById("field-editor-close");
|
|
9
|
+
|
|
10
|
+
let currentEditModule = null;
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Open / close
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
async function openFieldEditor(moduleName) {
|
|
17
|
+
currentEditModule = moduleName;
|
|
18
|
+
editorTitle.textContent = moduleName;
|
|
19
|
+
editorEl.classList.add("open");
|
|
20
|
+
|
|
21
|
+
// Fetch module data
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch("/api/modules");
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
const mod = data.modules.find((m) => m.moduleName === moduleName);
|
|
26
|
+
if (!mod) {
|
|
27
|
+
editorContent.innerHTML = "<p>Module not found</p>";
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fields = JSON.parse(mod.fieldsJson);
|
|
32
|
+
renderFieldForm(fields, moduleName);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
editorContent.innerHTML = `<p>Error: ${err.message}</p>`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function closeFieldEditor() {
|
|
39
|
+
editorEl.classList.remove("open");
|
|
40
|
+
currentEditModule = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
editorClose.addEventListener("click", closeFieldEditor);
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Render field form
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
function renderFieldForm(fields, moduleName, prefix = "") {
|
|
50
|
+
editorContent.innerHTML = "";
|
|
51
|
+
|
|
52
|
+
for (const field of fields) {
|
|
53
|
+
const fullPath = prefix ? `${prefix}.${field.name}` : field.name;
|
|
54
|
+
|
|
55
|
+
// Skip meta fields
|
|
56
|
+
if (field.name === "id") continue;
|
|
57
|
+
|
|
58
|
+
if (field.type === "group" && field.children) {
|
|
59
|
+
// Render group header
|
|
60
|
+
const group = document.createElement("div");
|
|
61
|
+
group.className = "field-group";
|
|
62
|
+
|
|
63
|
+
if (field.tab === "STYLE") {
|
|
64
|
+
const tabLabel = document.createElement("div");
|
|
65
|
+
tabLabel.className = "field-group__tab-label";
|
|
66
|
+
tabLabel.textContent = "Style";
|
|
67
|
+
group.appendChild(tabLabel);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const label = document.createElement("label");
|
|
71
|
+
label.className = "field-group__label";
|
|
72
|
+
label.textContent = field.label || field.name;
|
|
73
|
+
group.appendChild(label);
|
|
74
|
+
|
|
75
|
+
// Render children
|
|
76
|
+
for (const child of field.children) {
|
|
77
|
+
const childEl = createFieldInput(child, moduleName, `${fullPath}.${child.name}`);
|
|
78
|
+
if (childEl) group.appendChild(childEl);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
editorContent.appendChild(group);
|
|
82
|
+
} else {
|
|
83
|
+
const el = createFieldInput(field, moduleName, fullPath);
|
|
84
|
+
if (el) editorContent.appendChild(el);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Create field inputs by type
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
function createFieldInput(field, moduleName, fullPath) {
|
|
94
|
+
const group = document.createElement("div");
|
|
95
|
+
group.className = "field-group";
|
|
96
|
+
|
|
97
|
+
const label = document.createElement("label");
|
|
98
|
+
label.className = "field-group__label";
|
|
99
|
+
label.textContent = field.label || field.name;
|
|
100
|
+
group.appendChild(label);
|
|
101
|
+
|
|
102
|
+
switch (field.type) {
|
|
103
|
+
case "text": {
|
|
104
|
+
const input = document.createElement("input");
|
|
105
|
+
input.type = "text";
|
|
106
|
+
input.className = "field-input";
|
|
107
|
+
input.value = field.default || "";
|
|
108
|
+
input.addEventListener("change", () => {
|
|
109
|
+
updateField(moduleName, fullPath, input.value);
|
|
110
|
+
});
|
|
111
|
+
group.appendChild(input);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case "richtext": {
|
|
116
|
+
const textarea = document.createElement("textarea");
|
|
117
|
+
textarea.className = "field-input";
|
|
118
|
+
textarea.rows = 3;
|
|
119
|
+
textarea.value = field.default || "";
|
|
120
|
+
textarea.addEventListener("change", () => {
|
|
121
|
+
updateField(moduleName, fullPath, textarea.value);
|
|
122
|
+
});
|
|
123
|
+
group.appendChild(textarea);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case "color": {
|
|
128
|
+
const wrapper = document.createElement("div");
|
|
129
|
+
wrapper.className = "field-color";
|
|
130
|
+
|
|
131
|
+
const picker = document.createElement("input");
|
|
132
|
+
picker.type = "color";
|
|
133
|
+
picker.className = "field-color__picker";
|
|
134
|
+
picker.value = field.default?.color || "#000000";
|
|
135
|
+
|
|
136
|
+
const hex = document.createElement("input");
|
|
137
|
+
hex.type = "text";
|
|
138
|
+
hex.className = "field-input field-color__hex";
|
|
139
|
+
hex.value = field.default?.color || "#000000";
|
|
140
|
+
|
|
141
|
+
picker.addEventListener("input", () => {
|
|
142
|
+
hex.value = picker.value;
|
|
143
|
+
updateField(moduleName, fullPath, {
|
|
144
|
+
color: picker.value,
|
|
145
|
+
opacity: field.default?.opacity ?? 100,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
hex.addEventListener("change", () => {
|
|
150
|
+
picker.value = hex.value;
|
|
151
|
+
updateField(moduleName, fullPath, {
|
|
152
|
+
color: hex.value,
|
|
153
|
+
opacity: field.default?.opacity ?? 100,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
wrapper.appendChild(picker);
|
|
158
|
+
wrapper.appendChild(hex);
|
|
159
|
+
group.appendChild(wrapper);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case "image": {
|
|
164
|
+
const input = document.createElement("input");
|
|
165
|
+
input.type = "text";
|
|
166
|
+
input.className = "field-input";
|
|
167
|
+
input.placeholder = "Image URL";
|
|
168
|
+
input.value = field.default?.src || "";
|
|
169
|
+
input.addEventListener("change", () => {
|
|
170
|
+
updateField(moduleName, fullPath, {
|
|
171
|
+
src: input.value,
|
|
172
|
+
alt: field.default?.alt || "",
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
group.appendChild(input);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case "link": {
|
|
180
|
+
const input = document.createElement("input");
|
|
181
|
+
input.type = "text";
|
|
182
|
+
input.className = "field-input";
|
|
183
|
+
input.placeholder = "URL";
|
|
184
|
+
input.value = field.default?.url?.href || "";
|
|
185
|
+
input.addEventListener("change", () => {
|
|
186
|
+
updateField(moduleName, fullPath, {
|
|
187
|
+
url: { href: input.value, type: "EXTERNAL" },
|
|
188
|
+
open_in_new_tab: field.default?.open_in_new_tab ?? false,
|
|
189
|
+
no_follow: field.default?.no_follow ?? false,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
group.appendChild(input);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
case "number": {
|
|
197
|
+
const input = document.createElement("input");
|
|
198
|
+
input.type = "number";
|
|
199
|
+
input.className = "field-input";
|
|
200
|
+
input.value = field.default ?? 0;
|
|
201
|
+
input.addEventListener("change", () => {
|
|
202
|
+
updateField(moduleName, fullPath, Number(input.value));
|
|
203
|
+
});
|
|
204
|
+
group.appendChild(input);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
case "boolean": {
|
|
209
|
+
const wrapper = document.createElement("label");
|
|
210
|
+
wrapper.style.display = "flex";
|
|
211
|
+
wrapper.style.alignItems = "center";
|
|
212
|
+
wrapper.style.gap = "8px";
|
|
213
|
+
wrapper.style.cursor = "pointer";
|
|
214
|
+
|
|
215
|
+
const checkbox = document.createElement("input");
|
|
216
|
+
checkbox.type = "checkbox";
|
|
217
|
+
checkbox.checked = field.default ?? false;
|
|
218
|
+
checkbox.addEventListener("change", () => {
|
|
219
|
+
updateField(moduleName, fullPath, checkbox.checked);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const span = document.createElement("span");
|
|
223
|
+
span.textContent = field.label || field.name;
|
|
224
|
+
span.style.fontSize = "13px";
|
|
225
|
+
|
|
226
|
+
wrapper.appendChild(checkbox);
|
|
227
|
+
wrapper.appendChild(span);
|
|
228
|
+
group.innerHTML = "";
|
|
229
|
+
group.appendChild(wrapper);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case "choice": {
|
|
234
|
+
const select = document.createElement("select");
|
|
235
|
+
select.className = "field-input";
|
|
236
|
+
|
|
237
|
+
const choices = field.choices || [];
|
|
238
|
+
for (const choice of choices) {
|
|
239
|
+
const option = document.createElement("option");
|
|
240
|
+
if (Array.isArray(choice)) {
|
|
241
|
+
option.value = choice[0];
|
|
242
|
+
option.textContent = choice[1];
|
|
243
|
+
} else {
|
|
244
|
+
option.value = choice;
|
|
245
|
+
option.textContent = choice;
|
|
246
|
+
}
|
|
247
|
+
select.appendChild(option);
|
|
248
|
+
}
|
|
249
|
+
select.value = field.default || "";
|
|
250
|
+
select.addEventListener("change", () => {
|
|
251
|
+
updateField(moduleName, fullPath, select.value);
|
|
252
|
+
});
|
|
253
|
+
group.appendChild(select);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
default:
|
|
258
|
+
// Unknown field type — show as text input
|
|
259
|
+
if (typeof field.default === "string" || typeof field.default === "number") {
|
|
260
|
+
const input = document.createElement("input");
|
|
261
|
+
input.type = "text";
|
|
262
|
+
input.className = "field-input";
|
|
263
|
+
input.value = field.default ?? "";
|
|
264
|
+
input.addEventListener("change", () => {
|
|
265
|
+
updateField(moduleName, fullPath, input.value);
|
|
266
|
+
});
|
|
267
|
+
group.appendChild(input);
|
|
268
|
+
} else {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return group;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Update field and refresh preview
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
let updateTimer = null;
|
|
281
|
+
|
|
282
|
+
function updateField(moduleName, fieldPath, value) {
|
|
283
|
+
// Debounce updates
|
|
284
|
+
clearTimeout(updateTimer);
|
|
285
|
+
updateTimer = setTimeout(() => {
|
|
286
|
+
fetch("/api/field", {
|
|
287
|
+
method: "POST",
|
|
288
|
+
headers: { "Content-Type": "application/json" },
|
|
289
|
+
body: JSON.stringify({ moduleName, fieldPath, value }),
|
|
290
|
+
}).then(() => refreshPreview());
|
|
291
|
+
}, 300);
|
|
292
|
+
}
|