vibespot 1.2.0 → 1.3.1
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/README.md +54 -5
- package/assets/blog-rules.md +251 -0
- package/assets/email-rules.md +390 -0
- package/assets/humanify-guide.md +300 -101
- package/assets/plan-templates/blog-content-hub.md +18 -9
- package/assets/plan-templates/email-announcement.md +41 -0
- package/assets/plan-templates/email-event-invite.md +43 -0
- package/assets/plan-templates/email-newsletter.md +41 -0
- package/assets/plan-templates/email-re-engagement.md +42 -0
- package/assets/plan-templates/email-welcome.md +41 -0
- package/dist/index.js +1460 -387
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/starters/06-blog-content-hub.json +75 -0
- package/starters/06-email-welcome.json +60 -0
- package/starters/07-email-announcement.json +60 -0
- package/starters/08-email-newsletter.json +52 -0
- package/ui/chat.js +777 -63
- package/ui/code-editor.js +49 -7
- package/ui/dashboard.js +379 -93
- package/ui/docs/docs.css +29 -0
- package/ui/docs/index.html +416 -119
- package/ui/docs/screenshots/asset-type-cards.png +0 -0
- package/ui/docs/screenshots/brand-kit-preview.png +0 -0
- package/ui/docs/screenshots/content-type-dropdown.png +0 -0
- package/ui/docs/screenshots/deploy-progress.png +0 -0
- package/ui/docs/screenshots/editor-full-layout.png +0 -0
- package/ui/docs/screenshots/email-client-preview.png +0 -0
- package/ui/docs/screenshots/inline-wysiwyg-editing.png +0 -0
- package/ui/docs/screenshots/module-overview-slideout.png +0 -0
- package/ui/docs/screenshots/multi-page-tree.png +0 -0
- package/ui/docs/screenshots/onboarding-walkthrough.png +0 -0
- package/ui/docs/screenshots/pipeline-progress.png +0 -0
- package/ui/docs/screenshots/project-overview-table.png +0 -0
- package/ui/docs/screenshots/split-pane-view.png +0 -0
- package/ui/docs/screenshots/visual-controls-toolbar.png +0 -0
- package/ui/docs/screenshots/workspace-tabs.png +0 -0
- package/ui/email-preview.js +109 -0
- package/ui/field-editor.js +72 -1
- package/ui/icons.js +120 -0
- package/ui/index.html +877 -629
- package/ui/inline-edit.js +710 -0
- package/ui/plan.js +0 -0
- package/ui/preview.js +101 -198
- package/ui/section-controls.js +628 -0
- package/ui/settings.js +58 -16
- package/ui/setup.js +750 -140
- package/ui/styles.css +3430 -952
- package/ui/upload-panel.js +47 -20
package/ui/preview.js
CHANGED
|
@@ -3,12 +3,43 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const previewFrame = document.getElementById("preview-frame");
|
|
6
|
+
const previewEmptyState = document.getElementById("preview-empty-state");
|
|
6
7
|
|
|
7
8
|
// Highlights to apply once the iframe finishes loading after the next refresh.
|
|
8
9
|
let pendingChangedModules = null;
|
|
9
10
|
let pendingNewModules = null;
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Show or hide the preview empty state. Called when generation starts or when
|
|
14
|
+
* the iframe finishes loading and we can detect whether any modules rendered.
|
|
15
|
+
*/
|
|
16
|
+
function setPreviewEmptyState(show) {
|
|
17
|
+
if (!previewEmptyState) return;
|
|
18
|
+
previewEmptyState.setAttribute("aria-hidden", show ? "false" : "true");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Inspect iframe contents post-load and toggle the empty state accordingly.
|
|
23
|
+
* Empty state stays visible if the rendered preview has no module content.
|
|
24
|
+
*/
|
|
25
|
+
function syncEmptyStateFromFrame() {
|
|
26
|
+
if (!previewEmptyState) return;
|
|
27
|
+
try {
|
|
28
|
+
const doc = previewFrame.contentDocument || previewFrame.contentWindow.document;
|
|
29
|
+
if (!doc || !doc.body) {
|
|
30
|
+
setPreviewEmptyState(true);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const hasModules = doc.querySelector("[data-module]") !== null;
|
|
34
|
+
setPreviewEmptyState(!hasModules);
|
|
35
|
+
} catch {
|
|
36
|
+
// cross-origin — assume content is present
|
|
37
|
+
setPreviewEmptyState(false);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
11
41
|
previewFrame.addEventListener("load", () => {
|
|
42
|
+
syncEmptyStateFromFrame();
|
|
12
43
|
if (!pendingChangedModules && !pendingNewModules) return;
|
|
13
44
|
const changed = pendingChangedModules;
|
|
14
45
|
const fresh = pendingNewModules;
|
|
@@ -151,6 +182,7 @@ function scrollPreviewToModule(moduleName) {
|
|
|
151
182
|
* Called when AI generation starts to entertain the user while waiting.
|
|
152
183
|
*/
|
|
153
184
|
function showGeneratingPreview() {
|
|
185
|
+
setPreviewEmptyState(false);
|
|
154
186
|
const html = `<!DOCTYPE html>
|
|
155
187
|
<html lang="en">
|
|
156
188
|
<head>
|
|
@@ -381,215 +413,86 @@ function clearAllModulesWorking() {
|
|
|
381
413
|
// Preview refresh is triggered by setup.js after a session is created.
|
|
382
414
|
// Do NOT auto-refresh here.
|
|
383
415
|
|
|
416
|
+
// Select/edit mode has been unified into interact mode (inline-edit.js)
|
|
417
|
+
|
|
384
418
|
// ---------------------------------------------------------------------------
|
|
385
|
-
//
|
|
419
|
+
// HubL validity badge — aggregates per-module checks reported by chat.js into
|
|
420
|
+
// a single status pill in the preview toolbar.
|
|
386
421
|
// ---------------------------------------------------------------------------
|
|
387
422
|
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
let
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
let
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
.
|
|
407
|
-
outline: 2px solid #e8613a !important;
|
|
408
|
-
outline-offset: 2px !important;
|
|
409
|
-
background-color: rgba(232, 97, 58, 0.08) !important;
|
|
410
|
-
}
|
|
411
|
-
.vibespot-select-label {
|
|
412
|
-
position: fixed;
|
|
413
|
-
z-index: 2147483647;
|
|
414
|
-
pointer-events: none;
|
|
415
|
-
font: 500 11px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
416
|
-
color: #fff;
|
|
417
|
-
background: #e8613a;
|
|
418
|
-
padding: 3px 8px;
|
|
419
|
-
border-radius: 4px;
|
|
420
|
-
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
|
|
421
|
-
white-space: nowrap;
|
|
422
|
-
max-width: 70vw;
|
|
423
|
-
overflow: hidden;
|
|
424
|
-
text-overflow: ellipsis;
|
|
425
|
-
}
|
|
426
|
-
`;
|
|
427
|
-
doc.head.appendChild(style);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function describeElement(el) {
|
|
431
|
-
if (!el) return "";
|
|
432
|
-
const moduleEl = el.closest("[data-module]");
|
|
433
|
-
const moduleName = moduleEl ? moduleEl.getAttribute("data-module") : null;
|
|
434
|
-
const tag = (el.tagName || "").toLowerCase();
|
|
435
|
-
let kind = tag;
|
|
436
|
-
if (tag.match(/^h[1-6]$/)) kind = "headline";
|
|
437
|
-
else if (tag === "p") kind = "paragraph";
|
|
438
|
-
else if (tag === "a") kind = "link";
|
|
439
|
-
else if (tag === "button") kind = "button";
|
|
440
|
-
else if (tag === "img") kind = "image";
|
|
441
|
-
else if (tag === "ul" || tag === "ol") kind = "list";
|
|
442
|
-
else if (tag === "li") kind = "list item";
|
|
443
|
-
|
|
444
|
-
if (moduleName && moduleEl === el) return moduleName;
|
|
445
|
-
if (moduleName) return `${moduleName} > ${kind}`;
|
|
446
|
-
return kind;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function buildChatPrefill(el) {
|
|
450
|
-
if (!el) return "";
|
|
451
|
-
const moduleEl = el.closest("[data-module]");
|
|
452
|
-
const moduleName = moduleEl ? moduleEl.getAttribute("data-module") : null;
|
|
453
|
-
const tag = (el.tagName || "").toLowerCase();
|
|
454
|
-
const text = (el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 80);
|
|
455
|
-
|
|
456
|
-
let elementPart;
|
|
457
|
-
if (tag.match(/^h[1-6]$/)) elementPart = "the headline";
|
|
458
|
-
else if (tag === "p") elementPart = "the paragraph";
|
|
459
|
-
else if (tag === "a") elementPart = "the link";
|
|
460
|
-
else if (tag === "button") elementPart = "the button";
|
|
461
|
-
else if (tag === "img") elementPart = "the image";
|
|
462
|
-
else elementPart = `the ${tag}`;
|
|
463
|
-
|
|
464
|
-
if (moduleEl === el && moduleName) {
|
|
465
|
-
return `In the ${moduleName} section, `;
|
|
466
|
-
}
|
|
467
|
-
if (moduleName) {
|
|
468
|
-
const quote = text ? ` ("${text}${text.length === 80 ? "…" : ""}")` : "";
|
|
469
|
-
return `In the ${moduleName} section, ${elementPart}${quote} `;
|
|
423
|
+
const hublBadgeEl = document.getElementById("hubl-badge");
|
|
424
|
+
const hublBadgeLabelEl = hublBadgeEl ? hublBadgeEl.querySelector(".hubl-badge__label") : null;
|
|
425
|
+
const hublBadgeCountEl = document.getElementById("hubl-badge-count");
|
|
426
|
+
const hublModuleIssues = new Map();
|
|
427
|
+
let hublBadgeReveal = null;
|
|
428
|
+
|
|
429
|
+
function applyHublBadgeState() {
|
|
430
|
+
if (!hublBadgeEl) return;
|
|
431
|
+
let totalIssues = 0;
|
|
432
|
+
for (const issues of hublModuleIssues.values()) totalIssues += issues.length;
|
|
433
|
+
const failedModules = Array.from(hublModuleIssues.values()).filter((arr) => arr.length > 0).length;
|
|
434
|
+
|
|
435
|
+
const state = totalIssues === 0 ? "valid" : "issues";
|
|
436
|
+
hublBadgeEl.dataset.state = state;
|
|
437
|
+
hublBadgeEl.classList.toggle("hubl-badge--valid", state === "valid");
|
|
438
|
+
hublBadgeEl.classList.toggle("hubl-badge--issues", state === "issues");
|
|
439
|
+
|
|
440
|
+
if (hublBadgeLabelEl) {
|
|
441
|
+
hublBadgeLabelEl.textContent = state === "valid" ? "Valid HubL" : "HubL issues";
|
|
470
442
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
if (hoverLabelEl && hoverLabelEl.parentNode) {
|
|
480
|
-
hoverLabelEl.parentNode.removeChild(hoverLabelEl);
|
|
481
|
-
}
|
|
482
|
-
hoverLabelEl = null;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function attachSelectHandlers() {
|
|
486
|
-
let doc;
|
|
487
|
-
try {
|
|
488
|
-
doc = previewFrame.contentDocument || previewFrame.contentWindow?.document;
|
|
489
|
-
} catch { return; }
|
|
490
|
-
if (!doc || !doc.body) return;
|
|
491
|
-
|
|
492
|
-
ensureSelectModeStyles(doc);
|
|
493
|
-
doc.documentElement.classList.add("vibespot-select-mode");
|
|
494
|
-
|
|
495
|
-
onMouseOver = (e) => {
|
|
496
|
-
const el = e.target;
|
|
497
|
-
if (!el || el === doc.body || el === doc.documentElement) return;
|
|
498
|
-
if (el.classList && el.classList.contains("vibespot-select-label")) return;
|
|
499
|
-
if (hoveredEl === el) return;
|
|
500
|
-
if (hoveredEl) hoveredEl.classList.remove("vibespot-select-hover");
|
|
501
|
-
hoveredEl = el;
|
|
502
|
-
el.classList.add("vibespot-select-hover");
|
|
503
|
-
|
|
504
|
-
if (!hoverLabelEl) {
|
|
505
|
-
hoverLabelEl = doc.createElement("div");
|
|
506
|
-
hoverLabelEl.className = "vibespot-select-label";
|
|
507
|
-
doc.body.appendChild(hoverLabelEl);
|
|
508
|
-
}
|
|
509
|
-
hoverLabelEl.textContent = describeElement(el);
|
|
510
|
-
const rect = el.getBoundingClientRect();
|
|
511
|
-
hoverLabelEl.style.top = Math.max(4, rect.top - 22) + "px";
|
|
512
|
-
hoverLabelEl.style.left = Math.max(4, rect.left) + "px";
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
onMouseOut = (e) => {
|
|
516
|
-
if (!e.relatedTarget) clearHover(doc);
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
onClick = (e) => {
|
|
520
|
-
e.preventDefault();
|
|
521
|
-
e.stopPropagation();
|
|
522
|
-
const prefill = buildChatPrefill(e.target);
|
|
523
|
-
deactivateSelectMode();
|
|
524
|
-
if (typeof window.prefillChatInput === "function") {
|
|
525
|
-
window.prefillChatInput(prefill);
|
|
443
|
+
if (hublBadgeCountEl) {
|
|
444
|
+
if (state === "valid") {
|
|
445
|
+
hublBadgeCountEl.classList.add("hidden");
|
|
446
|
+
hublBadgeCountEl.textContent = "";
|
|
447
|
+
} else {
|
|
448
|
+
hublBadgeCountEl.classList.remove("hidden");
|
|
449
|
+
hublBadgeCountEl.textContent = String(totalIssues);
|
|
526
450
|
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
onKeyDown = (e) => {
|
|
530
|
-
if (e.key === "Escape") deactivateSelectMode();
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
doc.addEventListener("mouseover", onMouseOver, true);
|
|
534
|
-
doc.addEventListener("mouseout", onMouseOut, true);
|
|
535
|
-
doc.addEventListener("click", onClick, true);
|
|
536
|
-
doc.addEventListener("keydown", onKeyDown, true);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function detachSelectHandlers() {
|
|
540
|
-
let doc;
|
|
541
|
-
try {
|
|
542
|
-
doc = previewFrame.contentDocument || previewFrame.contentWindow?.document;
|
|
543
|
-
} catch { return; }
|
|
544
|
-
if (!doc) return;
|
|
545
|
-
doc.documentElement.classList.remove("vibespot-select-mode");
|
|
546
|
-
clearHover(doc);
|
|
547
|
-
if (onMouseOver) doc.removeEventListener("mouseover", onMouseOver, true);
|
|
548
|
-
if (onMouseOut) doc.removeEventListener("mouseout", onMouseOut, true);
|
|
549
|
-
if (onClick) doc.removeEventListener("click", onClick, true);
|
|
550
|
-
if (onKeyDown) doc.removeEventListener("keydown", onKeyDown, true);
|
|
551
|
-
onMouseOver = onMouseOut = onClick = onKeyDown = null;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function activateSelectMode() {
|
|
555
|
-
if (selectModeActive) return;
|
|
556
|
-
selectModeActive = true;
|
|
557
|
-
if (selectModeBtn) selectModeBtn.setAttribute("aria-pressed", "true");
|
|
558
|
-
if (previewContainer) previewContainer.classList.add("preview--select-mode");
|
|
559
|
-
attachSelectHandlers();
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function deactivateSelectMode() {
|
|
563
|
-
if (!selectModeActive) return;
|
|
564
|
-
selectModeActive = false;
|
|
565
|
-
if (selectModeBtn) selectModeBtn.setAttribute("aria-pressed", "false");
|
|
566
|
-
if (previewContainer) previewContainer.classList.remove("preview--select-mode");
|
|
567
|
-
detachSelectHandlers();
|
|
568
|
-
}
|
|
451
|
+
}
|
|
569
452
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
453
|
+
if (state === "valid") {
|
|
454
|
+
const checked = hublModuleIssues.size;
|
|
455
|
+
hublBadgeEl.title = checked === 0
|
|
456
|
+
? "HubL syntax check — no modules generated yet."
|
|
457
|
+
: `HubL syntax check — all ${checked} module${checked === 1 ? "" : "s"} parse cleanly.`;
|
|
575
458
|
} else {
|
|
576
|
-
|
|
459
|
+
hublBadgeEl.title = `${totalIssues} HubL issue${totalIssues === 1 ? "" : "s"} across ${failedModules} module${failedModules === 1 ? "" : "s"}. Click to review.`;
|
|
577
460
|
}
|
|
578
461
|
}
|
|
579
462
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
463
|
+
function flashHublBadge() {
|
|
464
|
+
if (!hublBadgeEl) return;
|
|
465
|
+
hublBadgeEl.classList.remove("hubl-badge--flash");
|
|
466
|
+
// Force reflow so the animation restarts.
|
|
467
|
+
void hublBadgeEl.offsetWidth;
|
|
468
|
+
hublBadgeEl.classList.add("hubl-badge--flash");
|
|
586
469
|
}
|
|
587
470
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
471
|
+
window.resetHublCheck = function () {
|
|
472
|
+
hublModuleIssues.clear();
|
|
473
|
+
applyHublBadgeState();
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
window.reportHublCheck = function (moduleName, issues) {
|
|
477
|
+
if (!moduleName) return;
|
|
478
|
+
hublModuleIssues.set(moduleName, Array.isArray(issues) ? issues : []);
|
|
479
|
+
applyHublBadgeState();
|
|
480
|
+
flashHublBadge();
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
if (hublBadgeEl) {
|
|
484
|
+
applyHublBadgeState();
|
|
485
|
+
hublBadgeEl.addEventListener("click", () => {
|
|
486
|
+
if (hublBadgeEl.dataset.state !== "issues") return;
|
|
487
|
+
const lines = [];
|
|
488
|
+
for (const [name, issues] of hublModuleIssues) {
|
|
489
|
+
if (!issues.length) continue;
|
|
490
|
+
lines.push(`• ${name}: ${issues.map((i) => i.message || i.kind).join(", ")}`);
|
|
491
|
+
}
|
|
492
|
+
if (typeof appendSystemMessage === "function") {
|
|
493
|
+
appendSystemMessage(`HubL issues in current run:\n${lines.join("\n")}`);
|
|
494
|
+
} else {
|
|
495
|
+
console.warn("HubL issues:\n" + lines.join("\n"));
|
|
496
|
+
}
|
|
591
497
|
});
|
|
592
498
|
}
|
|
593
|
-
|
|
594
|
-
window.setSelectModeDisabled = setSelectModeDisabled;
|
|
595
|
-
window.deactivateSelectMode = deactivateSelectMode;
|