@allegrocdp/preview 0.1.0-dev.6 → 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.
- package/dist/cli.js +13 -171
- package/dist/client/assets/index-Bz9RtHcN.js +96 -0
- package/dist/client/index.html +645 -1000
- package/dist/server.js +12 -170
- package/package.json +2 -6
- package/dist/client/assets/index-CDDVjT8Y.js +0 -137
package/dist/cli.js
CHANGED
|
@@ -6,116 +6,11 @@ import { Command } from "commander";
|
|
|
6
6
|
// src/server.ts
|
|
7
7
|
import chokidar from "chokidar";
|
|
8
8
|
import express from "express";
|
|
9
|
-
import { existsSync,
|
|
9
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
10
10
|
import open from "open";
|
|
11
11
|
import { basename, isAbsolute, join, relative, resolve } from "path";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
var __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
14
|
-
var BOILERPLATE_HTML = `<div class="signup">
|
|
15
|
-
<div class="signup__eyebrow">Newsletter</div>
|
|
16
|
-
<h2 class="signup__headline">{% headline %}</h2>
|
|
17
|
-
<p class="signup__sub">{% subheadline %}</p>
|
|
18
|
-
<form class="signup__form" onsubmit="return false">
|
|
19
|
-
<div class="signup__row">
|
|
20
|
-
<input class="signup__input" type="email" placeholder="{% input_placeholder %}" />
|
|
21
|
-
<button class="signup__btn" type="submit">{% button_text %}</button>
|
|
22
|
-
</div>
|
|
23
|
-
<p class="signup__note">{% privacy_note %}</p>
|
|
24
|
-
</form>
|
|
25
|
-
</div>
|
|
26
|
-
`;
|
|
27
|
-
var BOILERPLATE_CSS = `*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
28
|
-
|
|
29
|
-
.signup {
|
|
30
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', system-ui, sans-serif;
|
|
31
|
-
-webkit-font-smoothing: antialiased;
|
|
32
|
-
padding: 16px;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.signup__eyebrow {
|
|
36
|
-
font-size: 11px;
|
|
37
|
-
font-weight: 700;
|
|
38
|
-
letter-spacing: 0.1em;
|
|
39
|
-
text-transform: uppercase;
|
|
40
|
-
color: #6366f1;
|
|
41
|
-
margin-bottom: 8px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.signup__headline {
|
|
45
|
-
font-size: clamp(20px, 4vw, 26px);
|
|
46
|
-
font-weight: 800;
|
|
47
|
-
color: #0f172a;
|
|
48
|
-
line-height: 1.2;
|
|
49
|
-
letter-spacing: -0.02em;
|
|
50
|
-
margin-bottom: 8px;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.signup__sub {
|
|
54
|
-
font-size: 14px;
|
|
55
|
-
color: #64748b;
|
|
56
|
-
line-height: 1.6;
|
|
57
|
-
margin-bottom: 20px;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.signup__row {
|
|
61
|
-
display: flex;
|
|
62
|
-
gap: 8px;
|
|
63
|
-
flex-wrap: wrap;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.signup__input {
|
|
67
|
-
flex: 1;
|
|
68
|
-
min-width: 180px;
|
|
69
|
-
height: 42px;
|
|
70
|
-
padding: 0 12px;
|
|
71
|
-
border: 1.5px solid #e2e8f0;
|
|
72
|
-
border-radius: 8px;
|
|
73
|
-
font-size: 14px;
|
|
74
|
-
color: #0f172a;
|
|
75
|
-
background: #f8fafc;
|
|
76
|
-
outline: none;
|
|
77
|
-
transition: border-color 0.15s, background 0.15s;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.signup__input:focus { border-color: #6366f1; background: #fff; }
|
|
81
|
-
.signup__input::placeholder { color: #94a3b8; }
|
|
82
|
-
|
|
83
|
-
.signup__btn {
|
|
84
|
-
height: 42px;
|
|
85
|
-
padding: 0 20px;
|
|
86
|
-
background: #6366f1;
|
|
87
|
-
color: #fff;
|
|
88
|
-
border: none;
|
|
89
|
-
border-radius: 8px;
|
|
90
|
-
font-size: 14px;
|
|
91
|
-
font-weight: 600;
|
|
92
|
-
font-family: inherit;
|
|
93
|
-
cursor: pointer;
|
|
94
|
-
white-space: nowrap;
|
|
95
|
-
transition: background 0.15s;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.signup__btn:hover { background: #4f46e5; }
|
|
99
|
-
.signup__btn:active { opacity: 0.9; }
|
|
100
|
-
|
|
101
|
-
.signup__note {
|
|
102
|
-
margin-top: 10px;
|
|
103
|
-
font-size: 12px;
|
|
104
|
-
color: #94a3b8;
|
|
105
|
-
}
|
|
106
|
-
`;
|
|
107
|
-
var BOILERPLATE_FIELDS = [
|
|
108
|
-
{ slug: "headline", label: "Headline", type: "text", default_value: "Stay in the know" },
|
|
109
|
-
{
|
|
110
|
-
slug: "subheadline",
|
|
111
|
-
label: "Subheadline",
|
|
112
|
-
type: "textarea",
|
|
113
|
-
default_value: "Get the latest stories delivered to your inbox."
|
|
114
|
-
},
|
|
115
|
-
{ slug: "input_placeholder", label: "Input placeholder", type: "text", default_value: "Enter your email" },
|
|
116
|
-
{ slug: "button_text", label: "Button text", type: "text", default_value: "Subscribe" },
|
|
117
|
-
{ slug: "privacy_note", label: "Privacy note", type: "text", default_value: "No spam. Unsubscribe anytime." }
|
|
118
|
-
];
|
|
119
14
|
var sseClients = /* @__PURE__ */ new Set();
|
|
120
15
|
function broadcast(event) {
|
|
121
16
|
for (const client of sseClients) {
|
|
@@ -228,36 +123,6 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
228
123
|
}
|
|
229
124
|
res.json(readTemplateFiles(resolvedDir));
|
|
230
125
|
});
|
|
231
|
-
app.post("/api/template/create", express.json(), (req, res) => {
|
|
232
|
-
const body = req.body;
|
|
233
|
-
const slug = body.slug;
|
|
234
|
-
if (typeof slug !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
|
|
235
|
-
res.status(400).json({
|
|
236
|
-
error: "Slug must be lowercase letters, numbers, and hyphens only (e.g. my-template)."
|
|
237
|
-
});
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const targetDir = join(baseDir, slug);
|
|
241
|
-
if (existsSync(targetDir)) {
|
|
242
|
-
res.status(400).json({ error: `A template named "${slug}" already exists.` });
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
try {
|
|
246
|
-
mkdirSync(targetDir);
|
|
247
|
-
writeFileSync(join(targetDir, "index.html"), BOILERPLATE_HTML);
|
|
248
|
-
writeFileSync(join(targetDir, "index.css"), BOILERPLATE_CSS);
|
|
249
|
-
writeFileSync(
|
|
250
|
-
join(targetDir, "template.json"),
|
|
251
|
-
JSON.stringify({ name: slug, fields: BOILERPLATE_FIELDS }, null, 2) + "\n"
|
|
252
|
-
);
|
|
253
|
-
} catch (err) {
|
|
254
|
-
console.error("[allegro-preview] Failed to create template:", err);
|
|
255
|
-
res.status(500).json({ error: "Failed to create template files." });
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
rescan();
|
|
259
|
-
res.status(201).json({ slug });
|
|
260
|
-
});
|
|
261
126
|
app.get("/api/events", (req, res) => {
|
|
262
127
|
res.setHeader("Content-Type", "text/event-stream");
|
|
263
128
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -273,9 +138,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
273
138
|
if (existsSync(indexHtmlPath)) {
|
|
274
139
|
res.type("html").send(readFileSync(indexHtmlPath));
|
|
275
140
|
} else {
|
|
276
|
-
res.status(503).send(
|
|
277
|
-
'Preview client build artifacts not found. Ensure the client is built (for example, by running "npm run build" in this package during development).'
|
|
278
|
-
);
|
|
141
|
+
res.status(503).send('Preview client build artifacts not found. Ensure the client is built (for example, by running "npm run build" in this package during development).');
|
|
279
142
|
}
|
|
280
143
|
});
|
|
281
144
|
const TEMPLATE_FILES = ["index.html", "index.css", "index.js", "template.json"];
|
|
@@ -290,14 +153,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
290
153
|
console.log(`Changed: ${changedPath}`);
|
|
291
154
|
broadcastReload();
|
|
292
155
|
});
|
|
293
|
-
|
|
294
|
-
console.log(`Added: ${addedPath}`);
|
|
295
|
-
if (basename(addedPath) === "index.html") {
|
|
296
|
-
rescan();
|
|
297
|
-
}
|
|
298
|
-
broadcastReload();
|
|
299
|
-
});
|
|
300
|
-
const dirWatcher = chokidar.watch(baseDir, { depth: 1, ignoreInitial: true });
|
|
156
|
+
const dirWatcher = chokidar.watch(baseDir, { depth: 0, ignoreInitial: true });
|
|
301
157
|
function rescan() {
|
|
302
158
|
templates = scanTemplates(baseDir);
|
|
303
159
|
console.log(`Templates updated: ${templates.map((t) => t.slug).join(", ") || "(none)"}`);
|
|
@@ -324,37 +180,23 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
324
180
|
fileWatcher.add(templateFilePaths(join(filePath, "..")));
|
|
325
181
|
}
|
|
326
182
|
});
|
|
327
|
-
|
|
328
|
-
const server = app.listen(
|
|
329
|
-
server.once("listening", () => {
|
|
183
|
+
await new Promise((resolvePromise) => {
|
|
184
|
+
const server = app.listen(port, () => {
|
|
330
185
|
const addr = server.address();
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
server.once("error", reject);
|
|
335
|
-
});
|
|
336
|
-
let assignedPort;
|
|
337
|
-
try {
|
|
338
|
-
({ assignedPort } = await bindServer(port));
|
|
339
|
-
} catch (err) {
|
|
340
|
-
const nodeErr = err;
|
|
341
|
-
if (nodeErr.code === "EADDRINUSE" && port !== 0) {
|
|
342
|
-
console.log(`Port ${port} is in use, using a random available port instead.`);
|
|
343
|
-
({ assignedPort } = await bindServer(0));
|
|
344
|
-
} else {
|
|
345
|
-
throw err;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
const url = `http://localhost:${assignedPort}`;
|
|
349
|
-
console.log(`
|
|
186
|
+
const assignedPort = typeof addr === "object" && addr ? addr.port : port;
|
|
187
|
+
const url = `http://localhost:${assignedPort}`;
|
|
188
|
+
console.log(`
|
|
350
189
|
allegro-preview \u2192 ${url}
|
|
351
190
|
`);
|
|
352
|
-
|
|
191
|
+
void open(url);
|
|
192
|
+
resolvePromise();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
353
195
|
}
|
|
354
196
|
|
|
355
197
|
// src/cli.ts
|
|
356
198
|
var program = new Command();
|
|
357
|
-
program.name("allegro-preview").description("Local dev server for previewing Allegro templates").argument("[path]", "Path to the template folder", process.cwd()).option("-p, --port <number>", "Port to listen on (default:
|
|
199
|
+
program.name("allegro-preview").description("Local dev server for previewing Allegro templates").argument("[path]", "Path to the template folder", process.cwd()).option("-p, --port <number>", "Port to listen on (default: random available port)", "0").option("--tenant <url>", "Tenant base URL to pre-fill (overrides ALLEGRO_TENANT env var)").action(async (templatePath, options) => {
|
|
358
200
|
const port = parseInt(options.port, 10);
|
|
359
201
|
const tenant = options.tenant ?? process.env.ALLEGRO_TENANT ?? "";
|
|
360
202
|
await startServer(templatePath, port, tenant);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))o(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const s of r.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&o(s)}).observe(document,{childList:!0,subtree:!0});function n(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(i){if(i.ep)return;i.ep=!0;const r=n(i);fetch(i.href,r)}})();const q="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js";function V({html:e,css:t,js:n,externalCssUrls:o,interactive:i}){const r=o.map(l=>`<link rel="stylesheet" href="${l}">`).join(`
|
|
2
|
+
`),s="",a=n?n.replace(/<\/script>/gi,"<\\/script>"):"";return`<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<script>window.__ALLEGRO_PREVIEW__ = true;<\/script>
|
|
8
|
+
<script defer src="${q}"><\/script>
|
|
9
|
+
${r}
|
|
10
|
+
<style>${s}${t}</style>
|
|
11
|
+
</head>
|
|
12
|
+
<body>${e}${a?`<script>${a}<\/script>`:""}</body>
|
|
13
|
+
</html>`}function $(e,t){let n=e;for(const o of t){const i=new RegExp(`\\{%\\s*${o.slug}\\s*%\\}`,"g");n=n.replace(i,o.value)}return n}const U={full:0,desktop:1280,tablet:768,mobile:390},z=`
|
|
14
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
15
|
+
* { margin: 0; }
|
|
16
|
+
body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.5; -webkit-font-smoothing: antialiased; }
|
|
17
|
+
img, picture, video, canvas, svg { display: block; max-width: 100%; }
|
|
18
|
+
input, button, textarea, select { font: inherit; }
|
|
19
|
+
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
|
|
20
|
+
`,N=["The city council voted unanimously Tuesday to approve a sweeping new infrastructure plan that would reshape public transit routes across the metropolitan area over the next decade.","Mayor Jordan Ellison called the decision a turning point for how the region thinks about getting around, noting that similar investments in peer cities have led to measurable reductions in traffic congestion and improved air quality.","The plan includes upgrades to 14 existing bus rapid transit lines, the introduction of three new light-rail corridors connecting the downtown core to outlying neighborhoods, and dedicated cycling infrastructure along six major arterials.","Critics, however, warn that the projected cost of $2.4 billion may be optimistic. Independent analysts suggest the final figure could climb to $3.8 billion once land acquisition, labor, and contingency costs are factored in over the full construction timeline.","Council member Dana Reyes, who voted in favor, acknowledged the fiscal concern but argued that inaction carries its own price, noting that congestion costs residents and businesses hundreds of millions in lost productivity each year.","Environmental groups praised the announcement, saying expanded transit capacity is essential to meet the stated goal of reducing transportation emissions by 45 percent before 2035.","Public comment sessions are scheduled for next month at community centers in all twelve districts. Construction is expected to begin no sooner than mid-next year, pending environmental review and final design approvals."];function j(e){return e?`<script src="${e}"><\/script>`:""}function H(e,t,n,o){const i=JSON.stringify(e),r=JSON.stringify(t),s=JSON.stringify(n);return`(function() {
|
|
21
|
+
var html = ${i};
|
|
22
|
+
var css = ${r};
|
|
23
|
+
var js = ${s};
|
|
24
|
+
|
|
25
|
+
var host = document.createElement('div');
|
|
26
|
+
host.setAttribute('data-allegro-preview', '');
|
|
27
|
+
var shadow = host.attachShadow({ mode: 'open' });
|
|
28
|
+
|
|
29
|
+
if (css) {
|
|
30
|
+
var style = document.createElement('style');
|
|
31
|
+
style.textContent = css;
|
|
32
|
+
shadow.appendChild(style);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var container = document.createElement('div');
|
|
36
|
+
container.innerHTML = html;
|
|
37
|
+
shadow.appendChild(container);
|
|
38
|
+
|
|
39
|
+
if (js) {
|
|
40
|
+
try { (new Function('shadowRoot', 'hostElement', js))(shadow, host); }
|
|
41
|
+
catch(e) { console.error('[allegro-preview] JS error:', e); }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var article = document.getElementById('article-body');
|
|
45
|
+
if (!article) return;
|
|
46
|
+
|
|
47
|
+
var blockTags = new Set(['P','H1','H2','H3','H4','H5','H6','LI','BLOCKQUOTE','DIV','SECTION','FIGURE','PRE']);
|
|
48
|
+
var blocks = Array.from(article.children).filter(function(c) { return blockTags.has(c.tagName); });
|
|
49
|
+
|
|
50
|
+
${o==="end"?"article.appendChild(host);":`var cutIndex = Math.min(${o}, blocks.length);
|
|
51
|
+
var pivot = blocks[cutIndex - 1] || blocks[0];
|
|
52
|
+
pivot.after(host);
|
|
53
|
+
blocks.slice(cutIndex).forEach(function(el) { el.remove(); });`}
|
|
54
|
+
})();`}function M(e,t,n){const o=e.map(i=>`<p>${i}</p>`).join(`
|
|
55
|
+
`);return`<!DOCTYPE html>
|
|
56
|
+
<html>
|
|
57
|
+
<head>
|
|
58
|
+
<meta charset="UTF-8">
|
|
59
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
60
|
+
${j(n)}
|
|
61
|
+
<style>
|
|
62
|
+
* { box-sizing: border-box; }
|
|
63
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
64
|
+
body { font-family: Georgia, 'Times New Roman', serif; background: #fff; color: #111;
|
|
65
|
+
max-width: 680px; margin: 0 auto; padding: 32px 24px 64px; line-height: 1.75;
|
|
66
|
+
-webkit-font-smoothing: antialiased; }
|
|
67
|
+
h1 { font-size: 1.6rem; line-height: 1.25; margin: 0 0 6px; font-weight: 700; }
|
|
68
|
+
.meta { color: #6b7280; font-size: 0.82rem; font-family: system-ui, sans-serif; margin-bottom: 24px; }
|
|
69
|
+
p { margin: 0 0 1.1em; font-size: 1rem; overflow-wrap: break-word; }
|
|
70
|
+
[data-allegro-preview] { display: block; margin: 24px 0; }
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<header>
|
|
75
|
+
<h1>Council Approves Decade-Long Transit Overhaul</h1>
|
|
76
|
+
<p class="meta">By Staff Reporter · March 14, 2026</p>
|
|
77
|
+
</header>
|
|
78
|
+
<article id="article-body">
|
|
79
|
+
${o}
|
|
80
|
+
</article>
|
|
81
|
+
<script>${t}<\/script>
|
|
82
|
+
</body>
|
|
83
|
+
</html>`}function J(e,t,n){const o=j(n);return V({html:o+$(e.html,t),css:z+e.css,js:e.js,externalCssUrls:[],interactive:!0})}function G(e,t,n){const o=$(e.html,t),i=H(o,e.css,e.js,"end");return M(N,i,n)}function W(e,t,n){const o=$(e.html,t),i=H(o,e.css,e.js,3);return M(N,i,n)}function Y(e){return U[e]}let h=[],d=null,g="standalone",v="full",p=null,f={},b;const k=document.getElementById("template-nav"),u=document.getElementById("field-list"),c=document.getElementById("preview-area"),E=document.getElementById("tab-group"),x=document.getElementById("device-group"),C=document.getElementById("toolbar-badge"),K=document.getElementById("theme-toggle"),Q=document.getElementById("theme-icon-sun"),X=document.getElementById("theme-icon-moon"),w=document.getElementById("tenant-input"),y=document.getElementById("tenant-status");function A(e){document.documentElement.setAttribute("data-theme",e?"dark":"light"),Q.style.display=e?"none":"",X.style.display=e?"":"none"}function Z(){const e=localStorage.getItem("allegro-preview-theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches;A(e?e==="dark":t)}K.addEventListener("click",()=>{const e=document.documentElement.getAttribute("data-theme")!=="dark";A(e),localStorage.setItem("allegro-preview-theme",e?"dark":"light")});function ee(e){const t=e.trim();return t?(/^https?:\/\//.test(t)?t:`https://${t}`).replace(/\/+$/,"")+"/client.js":""}function P(e){const t=ee(e);b=t||void 0,t?(y.className="tenant-active-badge",y.textContent=t):(y.className="tenant-hint",y.textContent="Loads /client.js on the preview page"),p&&m()}w.addEventListener("input",()=>{localStorage.setItem("allegro-preview-tenant",w.value),P(w.value)});function te(e=""){const n=localStorage.getItem("allegro-preview-tenant")??e;n&&(w.value=n,P(n))}function ne(){const e=window.location.hash.slice(1);return e?decodeURIComponent(e):null}function oe(e){history.replaceState(null,"",`#${encodeURIComponent(e)}`)}function ie(){history.replaceState(null,"",window.location.pathname)}function B(e){E.querySelectorAll(".tab-btn").forEach(t=>{t.disabled=!e}),x.querySelectorAll(".device-btn").forEach(t=>{t.disabled=!e})}E.addEventListener("click",e=>{const t=e.target.closest(".tab-btn");!t||t.disabled||(g=t.dataset.tab,E.querySelectorAll(".tab-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-tab",g),m())});x.addEventListener("click",e=>{const t=e.target.closest(".device-btn");!t||t.disabled||(v=t.dataset.device,x.querySelectorAll(".device-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-device",v),m())});function S(){k.innerHTML="";for(const e of h){const t=document.createElement("button");t.className="template-nav-item"+(e.slug===d?" active":""),t.innerHTML=`<span class="template-nav-dot"></span><span class="template-nav-name">${L(e.name)}</span>`,t.addEventListener("click",()=>void F(e.slug)),k.appendChild(t)}}async function F(e){d=e,oe(e),S(),await T(e)}function O(){p=null,d=null,ie(),S(),B(!1),C.textContent="No template selected",C.style.opacity="0.4",u.innerHTML='<p class="no-fields">Select a template to edit fields.</p>';const e=document.createElement("div");if(e.className="selector-screen",h.length===0){e.innerHTML=`
|
|
84
|
+
<div class="selector-empty">
|
|
85
|
+
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
|
|
86
|
+
<span>No templates found in this directory.</span>
|
|
87
|
+
</div>`,c.innerHTML="",c.appendChild(e);return}e.innerHTML=`
|
|
88
|
+
<div class="selector-eyebrow">Allegro Preview</div>
|
|
89
|
+
<h2 class="selector-heading">Choose a template</h2>
|
|
90
|
+
<p class="selector-sub">Select a template to start previewing.</p>
|
|
91
|
+
<div class="selector-grid" id="selector-grid"></div>`,c.innerHTML="",c.appendChild(e);const t=e.querySelector("#selector-grid");for(const n of h){const o=document.createElement("button");o.className="selector-card",o.innerHTML=`
|
|
92
|
+
<div class="selector-card-icon">
|
|
93
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="selector-card-name">${L(n.name)}</div>
|
|
96
|
+
<div class="selector-card-slug">${L(n.slug)}</div>`,o.addEventListener("click",()=>void F(n.slug)),t.appendChild(o)}}const I=["text","number","checkbox","textarea"];function R(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.slug=="string"&&t.slug.length>0&&I.includes(t.type)}function se(e){if(typeof e!="object"||e===null)return`Invalid field: expected an object, got ${JSON.stringify(e)}.`;const t=e,n=[];if((typeof t.slug!="string"||t.slug.length===0)&&n.push('missing required "slug" property'),!I.includes(t.type)){const i=t.type!==void 0?`"${String(t.type)}"`:"none";n.push(`invalid type ${i} — valid types: ${I.join(", ")}`)}return`${typeof t.slug=="string"&&t.slug.length>0?`Field "${t.slug}"`:"Field"}: ${n.join("; ")}.`}function re(e){f={};for(const t of e)R(t)&&(f[t.slug]=t.default_value??"")}function ae(e,t){if(u.innerHTML="",t){const n=document.createElement("div");n.className="field-error",n.textContent=`template.json parse error: ${t}`,u.appendChild(n);return}if(e.length===0){u.innerHTML='<p class="no-fields">No fields in template.json.</p>';return}for(const n of e){if(!R(n)){const s=document.createElement("div");s.className="field-error",s.textContent=se(n),u.appendChild(s);continue}const o=n;if(o.type==="checkbox"){const s=document.createElement("div");s.className="field-checkbox-row";const a=document.createElement("input");a.type="checkbox",a.id=`field-${o.slug}`,a.checked=o.default_value==="true"||o.default_value==="1",a.addEventListener("change",()=>{f[o.slug]=a.checked?"true":"false",m()});const l=document.createElement("label");l.className="field-label",l.htmlFor=`field-${o.slug}`,l.textContent=o.label||o.slug,s.appendChild(a),s.appendChild(l),u.appendChild(s);continue}const i=document.createElement("div");i.className="field-group";const r=document.createElement("label");if(r.className="field-label",r.htmlFor=`field-${o.slug}`,r.textContent=o.label||o.slug,i.appendChild(r),o.type==="textarea"){const s=document.createElement("textarea");s.className="field-input",s.id=`field-${o.slug}`,s.rows=3,s.value=o.default_value??"",s.addEventListener("input",()=>{f[o.slug]=s.value,m()}),i.appendChild(s)}else{const s=document.createElement("input");s.type=o.type==="number"?"number":"text",s.className="field-input",s.id=`field-${o.slug}`,s.value=o.default_value??"",s.addEventListener("input",()=>{f[o.slug]=s.value,m()}),i.appendChild(s)}u.appendChild(i)}}function m(){if(!p)return;const e=Object.entries(f).map(([l,D])=>({slug:l,value:D})),t=Y(v),n=t===0,o=g==="standalone"?J(p,e,b):g==="append"?G(p,e,b):W(p,e,b);c.innerHTML="";const i=document.createElement("div");i.className=n?"preview-stage":"preview-stage preview-stage--breakpoint";const r=document.createElement("div");r.className="preview-scaler";const s=document.createElement("div");s.className="preview-frame-wrapper";const a=document.createElement("iframe");a.style.height="calc(100vh - 48px)",v==="mobile"&&(a.style.maxHeight="812px"),a.setAttribute("sandbox","allow-scripts allow-same-origin"),a.srcdoc=o,n?(s.style.width="100%",a.style.width="100%",r.style.width="100%"):(s.style.width=`${t}px`,a.style.width=`${t}px`),s.appendChild(a),r.appendChild(s),i.appendChild(r),c.appendChild(i)}async function T(e){try{const t=await fetch(`/api/template?slug=${encodeURIComponent(e)}`);if(!t.ok)throw new Error(`HTTP ${t.status}`);const n=await t.json();p=n,C.textContent=n.name,C.style.opacity="1",B(!0),re(n.fields),ae(n.fields,n.fieldsParseError),m()}catch(t){console.error("[allegro-preview] Failed to load template:",t),c.innerHTML="";const n=document.createElement("div");n.style.padding="40px",n.style.color="var(--text-muted)",n.style.fontSize="13px",n.textContent=`Failed to load template "${e}".`,c.appendChild(n)}}async function le(){const t=await(await fetch("/api/config")).json();h=t.templates,te(t.tenant??"");const n=ne(),o=n&&h.find(i=>i.slug===n)?n:null;o?(d=o,S(),await T(o)):O()}function ce(){const e=localStorage.getItem("allegro-preview-tab"),t=localStorage.getItem("allegro-preview-device");e&&["standalone","append","cut"].includes(e)&&(g=e,E.querySelectorAll(".tab-btn").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)})),t&&["full","desktop","tablet","mobile"].includes(t)&&(v=t,x.querySelectorAll(".device-btn").forEach(n=>{n.classList.toggle("active",n.dataset.device===t)}))}function _(){const e=new EventSource("/api/events");e.addEventListener("reload",()=>{d&&T(d)}),e.addEventListener("config",()=>{de()}),e.onerror=()=>{e.close(),setTimeout(_,2e3)}}async function de(){h=(await(await fetch("/api/config")).json()).templates,S(),d&&!h.find(n=>n.slug===d)&&O()}function L(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}Z();ce();le();_();
|