@allegrocdp/preview 0.1.0-dev.12 → 0.1.0-dev.13
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 +37 -10
- package/dist/client/assets/app-B0ybFOKs.js +14 -0
- package/dist/client/index.html +11 -4
- package/dist/server.js +37 -10
- package/package.json +1 -1
- package/dist/client/assets/app-D1XS_DDX.js +0 -14
package/dist/cli.js
CHANGED
|
@@ -27,6 +27,11 @@ window.__ALLEGRO_TEMPLATE_RESOLVER__ = async function (slug) {
|
|
|
27
27
|
}
|
|
28
28
|
};`;
|
|
29
29
|
|
|
30
|
+
// src/slugify.ts
|
|
31
|
+
function slugify(name) {
|
|
32
|
+
return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
// src/server.ts
|
|
31
36
|
var __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
32
37
|
var BOILERPLATE_HTML = `<div class="signup">
|
|
@@ -127,7 +132,7 @@ var BOILERPLATE_FIELDS = [
|
|
|
127
132
|
{
|
|
128
133
|
slug: "subheadline",
|
|
129
134
|
label: "Subheadline",
|
|
130
|
-
type: "
|
|
135
|
+
type: "text",
|
|
131
136
|
default_value: "Get the latest stories delivered to your inbox."
|
|
132
137
|
},
|
|
133
138
|
{ slug: "input_placeholder", label: "Input placeholder", type: "text", default_value: "Enter your email" },
|
|
@@ -310,7 +315,9 @@ function readTemplateMeta(dir) {
|
|
|
310
315
|
}
|
|
311
316
|
try {
|
|
312
317
|
const parsed = JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
313
|
-
return {
|
|
318
|
+
return {
|
|
319
|
+
name: typeof parsed.name === "string" && parsed.name.trim() !== "" ? parsed.name : basename(dir)
|
|
320
|
+
};
|
|
314
321
|
} catch {
|
|
315
322
|
return { name: basename(dir) };
|
|
316
323
|
}
|
|
@@ -412,16 +419,28 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
412
419
|
});
|
|
413
420
|
app.post("/api/template/create", express.json(), (req, res) => {
|
|
414
421
|
const body = req.body;
|
|
415
|
-
const
|
|
416
|
-
if (
|
|
417
|
-
res.status(400).json({
|
|
418
|
-
|
|
419
|
-
|
|
422
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
423
|
+
if (name === "") {
|
|
424
|
+
res.status(400).json({ error: "Template name is required." });
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const slug = slugify(name);
|
|
428
|
+
if (slug === "") {
|
|
429
|
+
res.status(400).json({ error: "Template name must contain at least one letter or number." });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const existing = scanTemplates(baseDir);
|
|
433
|
+
if (existing.some((t) => t.slug === slug)) {
|
|
434
|
+
res.status(400).json({ error: `A template with the slug "${slug}" already exists.` });
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (existing.some((t) => t.name.toLowerCase() === name.toLowerCase())) {
|
|
438
|
+
res.status(400).json({ error: `A template named "${name}" already exists.` });
|
|
420
439
|
return;
|
|
421
440
|
}
|
|
422
441
|
const targetDir = join(baseDir, slug);
|
|
423
442
|
if (existsSync(targetDir)) {
|
|
424
|
-
res.status(400).json({ error: `A template
|
|
443
|
+
res.status(400).json({ error: `A template with the slug "${slug}" already exists.` });
|
|
425
444
|
return;
|
|
426
445
|
}
|
|
427
446
|
try {
|
|
@@ -430,7 +449,15 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
430
449
|
writeFileSync(join(targetDir, "index.css"), BOILERPLATE_CSS);
|
|
431
450
|
writeFileSync(
|
|
432
451
|
join(targetDir, "template.json"),
|
|
433
|
-
JSON.stringify(
|
|
452
|
+
JSON.stringify(
|
|
453
|
+
{
|
|
454
|
+
$schema: "https://docs.allegrocdp.com/template.schema.json",
|
|
455
|
+
name,
|
|
456
|
+
fields: BOILERPLATE_FIELDS
|
|
457
|
+
},
|
|
458
|
+
null,
|
|
459
|
+
2
|
|
460
|
+
) + "\n"
|
|
434
461
|
);
|
|
435
462
|
} catch (err) {
|
|
436
463
|
console.error("[allegro-preview] Failed to create template:", err);
|
|
@@ -438,7 +465,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
438
465
|
return;
|
|
439
466
|
}
|
|
440
467
|
rescan();
|
|
441
|
-
res.status(201).json({ slug });
|
|
468
|
+
res.status(201).json({ slug, name });
|
|
442
469
|
});
|
|
443
470
|
app.get("/api/events", (req, res) => {
|
|
444
471
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))l(a);new MutationObserver(a=>{for(const s of a)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&l(o)}).observe(document,{childList:!0,subtree:!0});function n(a){const s={};return a.integrity&&(s.integrity=a.integrity),a.referrerPolicy&&(s.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?s.credentials="include":a.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function l(a){if(a.ep)return;a.ep=!0;const s=n(a);fetch(a.href,s)}})();function R(e){return e.normalize("NFKD").replace(/[\u0300-\u036f]/g,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}const le={full:0,desktop:1280,tablet:768,mobile:390};function oe(e){return le[e]}let r=[],u=null,N="standalone",y="full",L=null,E={},H,_=!0,T=null,f=null,h=null;const U=document.getElementById("template-nav"),g=document.getElementById("field-list"),m=document.getElementById("preview-area"),$=document.getElementById("tab-group"),M=document.getElementById("device-group"),j=document.getElementById("toolbar-badge"),ae=document.getElementById("theme-toggle"),se=document.getElementById("theme-icon-sun"),ie=document.getElementById("theme-icon-moon"),B=document.getElementById("tenant-input"),x=document.getElementById("tenant-status"),ce=document.getElementById("new-template-btn"),V=document.getElementById("new-window-btn"),k=document.getElementById("create-modal-backdrop"),c=document.getElementById("modal-name-input"),W=document.getElementById("modal-slug-hint"),v=document.getElementById("modal-error"),re=document.getElementById("modal-cancel-btn"),w=document.getElementById("modal-create-btn"),P=document.getElementById("block-cookies-checkbox"),G=document.getElementById("cookie-auth-area"),S=document.getElementById("cookie-auth-user"),J=document.getElementById("cookie-auth-hint"),de=document.getElementById("cookie-clear-btn"),b=document.getElementById("jwt-modal-backdrop"),ue=document.getElementById("jwt-payload-content"),me=document.getElementById("jwt-modal-close-btn");function z(e){document.documentElement.setAttribute("data-theme",e?"dark":"light"),se.style.display=e?"none":"",ie.style.display=e?"":"none"}function pe(){const e=localStorage.getItem("allegro-preview-theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches;z(e?e==="dark":t)}ae.addEventListener("click",()=>{const e=document.documentElement.getAttribute("data-theme")!=="dark";z(e),localStorage.setItem("allegro-preview-theme",e?"dark":"light")});function fe(e){const t=e.trim();return t?(/^https?:\/\//.test(t)?t:`https://${t}`).replace(/\/+$/,"")+"/client.js":""}function K(e){const t=fe(e);H=t||void 0,t?(x.className="tenant-active-badge",x.textContent=t):(x.className="tenant-hint",x.textContent="Loads /client.js on the preview page"),L&&p()}B.addEventListener("input",()=>{localStorage.setItem("allegro-preview-tenant",B.value),K(B.value)});function ge(e=""){const t=localStorage.getItem("allegro-preview-tenant"),n=e||t||"";n&&(B.value=n,e&&localStorage.setItem("allegro-preview-tenant",n),K(n))}const ve=[{name:"_ALLEGROT",label:"Session JWT",isJwt:!0},{name:"_ALLEGROD",label:"Device ID"},{name:"_ALLEGROS",label:"Session ID"}];function he(e){const t=document.cookie.split("; ").find(n=>n.startsWith(e+"="));if(!t)return null;try{return decodeURIComponent(t.split("=").slice(1).join("="))}catch{return t.split("=").slice(1).join("=")}}function ye(e){try{const t=e.split(".");if(t.length!==3)return null;const n=t[1].replace(/-/g,"+").replace(/_/g,"/"),l=atob(n);return JSON.parse(l)}catch{return null}}function C(){const e=ve.map(t=>({...t,value:he(t.name)})).filter(t=>t.value!==null);if(S.innerHTML="",e.length===0){S.style.display="none",J.style.display="";return}J.style.display="none",S.style.display="";for(const t of e){const n=document.createElement("div");n.className="cookie-row";const l=document.createElement("div");l.className="cookie-auth-dot";const a=document.createElement("div");a.className="cookie-row-info";const s=document.createElement("div");s.className="cookie-row-label",s.textContent=t.label;const o=document.createElement("div");if(o.className="cookie-row-value",o.textContent=t.value.length>24?t.value.slice(0,24)+"…":t.value,a.appendChild(s),a.appendChild(o),n.appendChild(l),n.appendChild(a),t.isJwt){const i=document.createElement("button");i.className="cookie-view-btn",i.textContent="Decode",i.onclick=()=>{const d=ye(t.value);ue.textContent=d?JSON.stringify(d,null,2):t.value,b.classList.remove("hidden")},n.appendChild(i)}S.appendChild(n)}}function Ee(e){_=e,localStorage.setItem("allegro-preview-block-cookies",e?"true":"false"),G.style.display=e?"none":"",e?h&&(clearInterval(h),h=null):(C(),h||(h=setInterval(C,2e3))),L&&p()}function we(){const t=localStorage.getItem("allegro-preview-block-cookies")!=="false";P.checked=t,_=t,G.style.display=t?"none":"",t||(C(),h=setInterval(C,2e3))}P.addEventListener("change",()=>{Ee(P.checked)});de.addEventListener("click",()=>{const e=document.cookie.split("; ");for(const t of e){const n=t.split("=")[0];document.cookie=`${n}=; max-age=0; path=/`}C()});me.addEventListener("click",()=>{b.classList.add("hidden")});b.addEventListener("click",e=>{e.target===b&&b.classList.add("hidden")});function be(){const e=window.location.hash.slice(1);return e?decodeURIComponent(e):null}function ke(e){history.replaceState(null,"",`#${encodeURIComponent(e)}`)}function Ce(){history.replaceState(null,"",window.location.pathname)}function Y(e){$.querySelectorAll(".tab-btn").forEach(t=>{t.disabled=!e}),M.querySelectorAll(".device-btn").forEach(t=>{t.disabled=!e}),V.disabled=!e}$.addEventListener("click",e=>{const t=e.target.closest(".tab-btn");!t||t.disabled||(N=t.dataset.tab,$.querySelectorAll(".tab-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-tab",N),p())});M.addEventListener("click",e=>{const t=e.target.closest(".device-btn");!t||t.disabled||(y=t.dataset.device,M.querySelectorAll(".device-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-device",y),p())});function I(){U.innerHTML="";for(const e of r){const t=document.createElement("button");t.className="template-nav-item"+(e.slug===u?" active":""),t.innerHTML=`<span class="template-nav-dot"></span><span class="template-nav-name">${F(e.name)}</span>`,t.addEventListener("click",()=>void D(e.slug)),U.appendChild(t)}}async function D(e){u=e,ke(e),I(),await q(e)}function Q(){L=null,u=null,T=null,f=null,Ce(),I(),Y(!1),j.textContent="No template selected",j.style.opacity="0.4",g.innerHTML='<p class="no-fields">Select a template to edit fields.</p>';const e=document.createElement("div");if(e.className="selector-screen",r.length===0){e.innerHTML=`
|
|
2
|
+
<div class="selector-empty">
|
|
3
|
+
<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>
|
|
4
|
+
<span>No templates found in this directory.</span>
|
|
5
|
+
</div>`,m.innerHTML="",m.appendChild(e);return}e.innerHTML=`
|
|
6
|
+
<div class="selector-eyebrow">Allegro Preview</div>
|
|
7
|
+
<h2 class="selector-heading">Choose a template</h2>
|
|
8
|
+
<p class="selector-sub">Select a template to start previewing.</p>
|
|
9
|
+
<div class="selector-grid" id="selector-grid"></div>`,m.innerHTML="",m.appendChild(e);const t=e.querySelector("#selector-grid");for(const n of r){const l=document.createElement("button");l.className="selector-card",l.innerHTML=`
|
|
10
|
+
<div class="selector-card-icon">
|
|
11
|
+
<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>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="selector-card-name">${F(n.name)}</div>
|
|
14
|
+
<div class="selector-card-slug">${F(n.slug)}</div>`,l.addEventListener("click",()=>void D(n.slug)),t.appendChild(l)}}const O=["text","number","checkbox","textarea"];function X(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.slug=="string"&&t.slug.length>0&&O.includes(t.type)}function Le(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'),!O.includes(t.type)){const a=t.type!==void 0?`"${String(t.type)}"`:"none";n.push(`invalid type ${a} — valid types: ${O.join(", ")}`)}return`${typeof t.slug=="string"&&t.slug.length>0?`Field "${t.slug}"`:"Field"}: ${n.join("; ")}.`}function Ie(e){E={};for(const t of e)X(t)&&(E[t.slug]=t.default_value??"")}function xe(e,t){if(g.innerHTML="",t){const n=document.createElement("div");n.className="field-error",n.textContent=`template.json parse error: ${t}`,g.appendChild(n);return}if(e.length===0){g.innerHTML='<p class="no-fields">No fields in template.json.</p>';return}for(const n of e){if(!X(n)){const o=document.createElement("div");o.className="field-error",o.textContent=Le(n),g.appendChild(o);continue}const l=n;if(l.type==="checkbox"){const o=document.createElement("div");o.className="field-checkbox-row";const i=document.createElement("input");i.type="checkbox",i.id=`field-${l.slug}`,i.checked=l.default_value==="true"||l.default_value==="1",i.addEventListener("change",()=>{E[l.slug]=i.checked?"true":"false",p()});const d=document.createElement("label");d.className="field-label",d.htmlFor=`field-${l.slug}`,d.textContent=l.label||l.slug,o.appendChild(i),o.appendChild(d),g.appendChild(o);continue}const a=document.createElement("div");a.className="field-group";const s=document.createElement("label");if(s.className="field-label",s.htmlFor=`field-${l.slug}`,s.textContent=l.label||l.slug,a.appendChild(s),l.type==="textarea"){const o=document.createElement("textarea");o.className="field-input",o.id=`field-${l.slug}`,o.rows=3,o.value=l.default_value??"",o.addEventListener("input",()=>{E[l.slug]=o.value,p()}),a.appendChild(o)}else{const o=document.createElement("input");o.type=l.type==="number"?"number":"text",o.className="field-input",o.id=`field-${l.slug}`,o.value=l.default_value??"",o.addEventListener("input",()=>{E[l.slug]=o.value,p()}),a.appendChild(o)}g.appendChild(a)}}function Se(){const e=new URLSearchParams;e.set("slug",u),e.set("tab",N),H&&e.set("sdk",H),_||e.set("blockCookies","false");for(const[t,n]of Object.entries(E))e.set(`f_${t}`,n);return`/preview?${e.toString()}`}function p(){if(!L||!u)return;const e=Se();T=e;const t=oe(y),n=t===0;if(f){const i=f.parentElement,d=i.parentElement,ne=d.parentElement;ne.className=n?"preview-stage":"preview-stage preview-stage--breakpoint",d.style.width=n?"100%":"",i.style.width=n?"100%":`${t}px`,f.style.width=n?"100%":`${t}px`,f.style.maxHeight=y==="mobile"?"812px":"",f.src=e;return}m.innerHTML="";const l=document.createElement("div");l.className=n?"preview-stage":"preview-stage preview-stage--breakpoint";const a=document.createElement("div");a.className="preview-scaler";const s=document.createElement("div");s.className="preview-frame-wrapper";const o=document.createElement("iframe");o.style.height="calc(100vh - 48px)",n?(a.style.width="100%",s.style.width="100%",o.style.width="100%"):(s.style.width=`${t}px`,o.style.width=`${t}px`,y==="mobile"&&(o.style.maxHeight="812px")),o.src=e,s.appendChild(o),a.appendChild(s),l.appendChild(a),m.appendChild(l),f=o}async function q(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();L=n,j.textContent=`Template: ${n.name}`,j.style.opacity="1",Y(!0),Ie(n.fields),xe(n.fields,n.fieldsParseError),p()}catch(t){console.error("[allegro-preview] Failed to load template:",t),m.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}".`,m.appendChild(n)}}async function Be(){const t=await(await fetch("/api/config")).json();r=t.templates,ge(t.tenant??"");const n=be(),l=n&&r.find(a=>a.slug===n)?n:null;l?(u=l,I(),await q(l)):Q()}function Ne(){const e=localStorage.getItem("allegro-preview-tab"),t=localStorage.getItem("allegro-preview-device");e&&["standalone","append","cut"].includes(e)&&(N=e,$.querySelectorAll(".tab-btn").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)})),t&&["full","desktop","tablet","mobile"].includes(t)&&(y=t,M.querySelectorAll(".device-btn").forEach(n=>{n.classList.toggle("active",n.dataset.device===t)}))}function Z(){const e=new EventSource("/api/events");e.addEventListener("reload",()=>{u&&q(u)}),e.addEventListener("config",()=>{Te()}),e.onerror=()=>{e.close(),setTimeout(Z,2e3)}}async function Te(){r=(await(await fetch("/api/config")).json()).templates,I(),u&&!r.find(n=>n.slug===u)&&Q()}function F(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function $e(){c.value="",v.textContent="",W.textContent="",c.classList.remove("invalid"),w.disabled=!0,k.classList.remove("hidden"),c.focus()}function A(){k.classList.add("hidden")}function ee(e){if(!e)return"Template name is required.";const t=R(e);return t?r.some(n=>n.slug===t)?`A template with the slug "${t}" already exists.`:r.some(n=>n.name.toLowerCase()===e.toLowerCase())?`A template named "${e}" already exists.`:null:"Template name must contain at least one letter or number."}V.addEventListener("click",()=>{T&&window.open(T,"_blank")});ce.addEventListener("click",$e);re.addEventListener("click",A);k.addEventListener("click",e=>{e.target===k&&A()});document.addEventListener("keydown",e=>{e.key==="Escape"&&!k.classList.contains("hidden")&&A()});c.addEventListener("input",()=>{const e=c.value.trim(),t=R(e);W.textContent=t?`Slug: ${t}`:"";const n=ee(e);w.disabled=n!==null,n&&e?(c.classList.add("invalid"),v.textContent=n):(c.classList.remove("invalid"),v.textContent="")});c.addEventListener("keydown",e=>{e.key==="Enter"&&te()});w.addEventListener("click",()=>void te());async function te(){const e=c.value.trim(),t=ee(e);if(t){c.classList.add("invalid"),v.textContent=t,c.focus();return}w.disabled=!0,v.textContent="";try{const n=await fetch("/api/template/create",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})}),l=await n.json();if(!n.ok||!l.slug){v.textContent=l.error??"Failed to create template.",w.disabled=!1;return}A(),r=[...r,{slug:l.slug,name:l.name??e}],I(),await D(l.slug)}catch{v.textContent="Network error. Please try again.",w.disabled=!1}}pe();Ne();we();Be();Z();
|
package/dist/client/index.html
CHANGED
|
@@ -283,6 +283,12 @@
|
|
|
283
283
|
border-color: #f87171;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
.modal-hint {
|
|
287
|
+
margin-top: 6px;
|
|
288
|
+
font-size: 12px;
|
|
289
|
+
color: #94a3b8;
|
|
290
|
+
}
|
|
291
|
+
|
|
286
292
|
.modal-error {
|
|
287
293
|
font-size: 11px;
|
|
288
294
|
color: #f87171;
|
|
@@ -1008,7 +1014,7 @@
|
|
|
1008
1014
|
display: block;
|
|
1009
1015
|
}
|
|
1010
1016
|
</style>
|
|
1011
|
-
<script type="module" crossorigin src="/assets/app-
|
|
1017
|
+
<script type="module" crossorigin src="/assets/app-B0ybFOKs.js"></script>
|
|
1012
1018
|
</head>
|
|
1013
1019
|
<body>
|
|
1014
1020
|
<aside class="sidebar">
|
|
@@ -1250,15 +1256,16 @@
|
|
|
1250
1256
|
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
|
1251
1257
|
<div class="modal-title" id="modal-title">New Template</div>
|
|
1252
1258
|
<div class="modal-field">
|
|
1253
|
-
<label class="modal-label" for="modal-
|
|
1259
|
+
<label class="modal-label" for="modal-name-input">Template name</label>
|
|
1254
1260
|
<input
|
|
1255
1261
|
class="modal-input"
|
|
1256
|
-
id="modal-
|
|
1262
|
+
id="modal-name-input"
|
|
1257
1263
|
type="text"
|
|
1258
|
-
placeholder="
|
|
1264
|
+
placeholder="My template"
|
|
1259
1265
|
autocomplete="off"
|
|
1260
1266
|
spellcheck="false"
|
|
1261
1267
|
/>
|
|
1268
|
+
<div class="modal-hint" id="modal-slug-hint"></div>
|
|
1262
1269
|
<div class="modal-error" id="modal-error"></div>
|
|
1263
1270
|
</div>
|
|
1264
1271
|
<div class="modal-actions">
|
package/dist/server.js
CHANGED
|
@@ -24,6 +24,11 @@ window.__ALLEGRO_TEMPLATE_RESOLVER__ = async function (slug) {
|
|
|
24
24
|
}
|
|
25
25
|
};`;
|
|
26
26
|
|
|
27
|
+
// src/slugify.ts
|
|
28
|
+
function slugify(name) {
|
|
29
|
+
return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
// src/server.ts
|
|
28
33
|
var __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
29
34
|
var BOILERPLATE_HTML = `<div class="signup">
|
|
@@ -124,7 +129,7 @@ var BOILERPLATE_FIELDS = [
|
|
|
124
129
|
{
|
|
125
130
|
slug: "subheadline",
|
|
126
131
|
label: "Subheadline",
|
|
127
|
-
type: "
|
|
132
|
+
type: "text",
|
|
128
133
|
default_value: "Get the latest stories delivered to your inbox."
|
|
129
134
|
},
|
|
130
135
|
{ slug: "input_placeholder", label: "Input placeholder", type: "text", default_value: "Enter your email" },
|
|
@@ -307,7 +312,9 @@ function readTemplateMeta(dir) {
|
|
|
307
312
|
}
|
|
308
313
|
try {
|
|
309
314
|
const parsed = JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
310
|
-
return {
|
|
315
|
+
return {
|
|
316
|
+
name: typeof parsed.name === "string" && parsed.name.trim() !== "" ? parsed.name : basename(dir)
|
|
317
|
+
};
|
|
311
318
|
} catch {
|
|
312
319
|
return { name: basename(dir) };
|
|
313
320
|
}
|
|
@@ -409,16 +416,28 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
409
416
|
});
|
|
410
417
|
app.post("/api/template/create", express.json(), (req, res) => {
|
|
411
418
|
const body = req.body;
|
|
412
|
-
const
|
|
413
|
-
if (
|
|
414
|
-
res.status(400).json({
|
|
415
|
-
|
|
416
|
-
|
|
419
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
420
|
+
if (name === "") {
|
|
421
|
+
res.status(400).json({ error: "Template name is required." });
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const slug = slugify(name);
|
|
425
|
+
if (slug === "") {
|
|
426
|
+
res.status(400).json({ error: "Template name must contain at least one letter or number." });
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const existing = scanTemplates(baseDir);
|
|
430
|
+
if (existing.some((t) => t.slug === slug)) {
|
|
431
|
+
res.status(400).json({ error: `A template with the slug "${slug}" already exists.` });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (existing.some((t) => t.name.toLowerCase() === name.toLowerCase())) {
|
|
435
|
+
res.status(400).json({ error: `A template named "${name}" already exists.` });
|
|
417
436
|
return;
|
|
418
437
|
}
|
|
419
438
|
const targetDir = join(baseDir, slug);
|
|
420
439
|
if (existsSync(targetDir)) {
|
|
421
|
-
res.status(400).json({ error: `A template
|
|
440
|
+
res.status(400).json({ error: `A template with the slug "${slug}" already exists.` });
|
|
422
441
|
return;
|
|
423
442
|
}
|
|
424
443
|
try {
|
|
@@ -427,7 +446,15 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
427
446
|
writeFileSync(join(targetDir, "index.css"), BOILERPLATE_CSS);
|
|
428
447
|
writeFileSync(
|
|
429
448
|
join(targetDir, "template.json"),
|
|
430
|
-
JSON.stringify(
|
|
449
|
+
JSON.stringify(
|
|
450
|
+
{
|
|
451
|
+
$schema: "https://docs.allegrocdp.com/template.schema.json",
|
|
452
|
+
name,
|
|
453
|
+
fields: BOILERPLATE_FIELDS
|
|
454
|
+
},
|
|
455
|
+
null,
|
|
456
|
+
2
|
|
457
|
+
) + "\n"
|
|
431
458
|
);
|
|
432
459
|
} catch (err) {
|
|
433
460
|
console.error("[allegro-preview] Failed to create template:", err);
|
|
@@ -435,7 +462,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
435
462
|
return;
|
|
436
463
|
}
|
|
437
464
|
rescan();
|
|
438
|
-
res.status(201).json({ slug });
|
|
465
|
+
res.status(201).json({ slug, name });
|
|
439
466
|
});
|
|
440
467
|
app.get("/api/events", (req, res) => {
|
|
441
468
|
res.setHeader("Content-Type", "text/event-stream");
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))l(a);new MutationObserver(a=>{for(const s of a)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&l(o)}).observe(document,{childList:!0,subtree:!0});function n(a){const s={};return a.integrity&&(s.integrity=a.integrity),a.referrerPolicy&&(s.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?s.credentials="include":a.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function l(a){if(a.ep)return;a.ep=!0;const s=n(a);fetch(a.href,s)}})();const te={full:0,desktop:1280,tablet:768,mobile:390};function ne(e){return te[e]}let u=[],d=null,N="standalone",y="full",L=null,E={},H,F=!0,T=null,f=null,h=null;const q=document.getElementById("template-nav"),g=document.getElementById("field-list"),m=document.getElementById("preview-area"),$=document.getElementById("tab-group"),M=document.getElementById("device-group"),j=document.getElementById("toolbar-badge"),le=document.getElementById("theme-toggle"),oe=document.getElementById("theme-icon-sun"),ae=document.getElementById("theme-icon-moon"),B=document.getElementById("tenant-input"),x=document.getElementById("tenant-status"),se=document.getElementById("new-template-btn"),J=document.getElementById("new-window-btn"),k=document.getElementById("create-modal-backdrop"),c=document.getElementById("modal-slug-input"),v=document.getElementById("modal-error"),ie=document.getElementById("modal-cancel-btn"),w=document.getElementById("modal-create-btn"),P=document.getElementById("block-cookies-checkbox"),V=document.getElementById("cookie-auth-area"),S=document.getElementById("cookie-auth-user"),R=document.getElementById("cookie-auth-hint"),ce=document.getElementById("cookie-clear-btn"),b=document.getElementById("jwt-modal-backdrop"),re=document.getElementById("jwt-payload-content"),de=document.getElementById("jwt-modal-close-btn");function W(e){document.documentElement.setAttribute("data-theme",e?"dark":"light"),oe.style.display=e?"none":"",ae.style.display=e?"":"none"}function ue(){const e=localStorage.getItem("allegro-preview-theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches;W(e?e==="dark":t)}le.addEventListener("click",()=>{const e=document.documentElement.getAttribute("data-theme")!=="dark";W(e),localStorage.setItem("allegro-preview-theme",e?"dark":"light")});function me(e){const t=e.trim();return t?(/^https?:\/\//.test(t)?t:`https://${t}`).replace(/\/+$/,"")+"/client.js":""}function G(e){const t=me(e);H=t||void 0,t?(x.className="tenant-active-badge",x.textContent=t):(x.className="tenant-hint",x.textContent="Loads /client.js on the preview page"),L&&p()}B.addEventListener("input",()=>{localStorage.setItem("allegro-preview-tenant",B.value),G(B.value)});function pe(e=""){const t=localStorage.getItem("allegro-preview-tenant"),n=e||t||"";n&&(B.value=n,e&&localStorage.setItem("allegro-preview-tenant",n),G(n))}const fe=[{name:"_ALLEGROT",label:"Session JWT",isJwt:!0},{name:"_ALLEGROD",label:"Device ID"},{name:"_ALLEGROS",label:"Session ID"}];function ge(e){const t=document.cookie.split("; ").find(n=>n.startsWith(e+"="));if(!t)return null;try{return decodeURIComponent(t.split("=").slice(1).join("="))}catch{return t.split("=").slice(1).join("=")}}function ve(e){try{const t=e.split(".");if(t.length!==3)return null;const n=t[1].replace(/-/g,"+").replace(/_/g,"/"),l=atob(n);return JSON.parse(l)}catch{return null}}function C(){const e=fe.map(t=>({...t,value:ge(t.name)})).filter(t=>t.value!==null);if(S.innerHTML="",e.length===0){S.style.display="none",R.style.display="";return}R.style.display="none",S.style.display="";for(const t of e){const n=document.createElement("div");n.className="cookie-row";const l=document.createElement("div");l.className="cookie-auth-dot";const a=document.createElement("div");a.className="cookie-row-info";const s=document.createElement("div");s.className="cookie-row-label",s.textContent=t.label;const o=document.createElement("div");if(o.className="cookie-row-value",o.textContent=t.value.length>24?t.value.slice(0,24)+"…":t.value,a.appendChild(s),a.appendChild(o),n.appendChild(l),n.appendChild(a),t.isJwt){const i=document.createElement("button");i.className="cookie-view-btn",i.textContent="Decode",i.onclick=()=>{const r=ve(t.value);re.textContent=r?JSON.stringify(r,null,2):t.value,b.classList.remove("hidden")},n.appendChild(i)}S.appendChild(n)}}function he(e){F=e,localStorage.setItem("allegro-preview-block-cookies",e?"true":"false"),V.style.display=e?"none":"",e?h&&(clearInterval(h),h=null):(C(),h||(h=setInterval(C,2e3))),L&&p()}function ye(){const t=localStorage.getItem("allegro-preview-block-cookies")!=="false";P.checked=t,F=t,V.style.display=t?"none":"",t||(C(),h=setInterval(C,2e3))}P.addEventListener("change",()=>{he(P.checked)});ce.addEventListener("click",()=>{const e=document.cookie.split("; ");for(const t of e){const n=t.split("=")[0];document.cookie=`${n}=; max-age=0; path=/`}C()});de.addEventListener("click",()=>{b.classList.add("hidden")});b.addEventListener("click",e=>{e.target===b&&b.classList.add("hidden")});function Ee(){const e=window.location.hash.slice(1);return e?decodeURIComponent(e):null}function we(e){history.replaceState(null,"",`#${encodeURIComponent(e)}`)}function be(){history.replaceState(null,"",window.location.pathname)}function z(e){$.querySelectorAll(".tab-btn").forEach(t=>{t.disabled=!e}),M.querySelectorAll(".device-btn").forEach(t=>{t.disabled=!e}),J.disabled=!e}$.addEventListener("click",e=>{const t=e.target.closest(".tab-btn");!t||t.disabled||(N=t.dataset.tab,$.querySelectorAll(".tab-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-tab",N),p())});M.addEventListener("click",e=>{const t=e.target.closest(".device-btn");!t||t.disabled||(y=t.dataset.device,M.querySelectorAll(".device-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-device",y),p())});function I(){q.innerHTML="";for(const e of u){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">${_(e.name)}</span>`,t.addEventListener("click",()=>void D(e.slug)),q.appendChild(t)}}async function D(e){d=e,we(e),I(),await U(e)}function K(){L=null,d=null,T=null,f=null,be(),I(),z(!1),j.textContent="No template selected",j.style.opacity="0.4",g.innerHTML='<p class="no-fields">Select a template to edit fields.</p>';const e=document.createElement("div");if(e.className="selector-screen",u.length===0){e.innerHTML=`
|
|
2
|
-
<div class="selector-empty">
|
|
3
|
-
<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>
|
|
4
|
-
<span>No templates found in this directory.</span>
|
|
5
|
-
</div>`,m.innerHTML="",m.appendChild(e);return}e.innerHTML=`
|
|
6
|
-
<div class="selector-eyebrow">Allegro Preview</div>
|
|
7
|
-
<h2 class="selector-heading">Choose a template</h2>
|
|
8
|
-
<p class="selector-sub">Select a template to start previewing.</p>
|
|
9
|
-
<div class="selector-grid" id="selector-grid"></div>`,m.innerHTML="",m.appendChild(e);const t=e.querySelector("#selector-grid");for(const n of u){const l=document.createElement("button");l.className="selector-card",l.innerHTML=`
|
|
10
|
-
<div class="selector-card-icon">
|
|
11
|
-
<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>
|
|
12
|
-
</div>
|
|
13
|
-
<div class="selector-card-name">${_(n.name)}</div>
|
|
14
|
-
<div class="selector-card-slug">${_(n.slug)}</div>`,l.addEventListener("click",()=>void D(n.slug)),t.appendChild(l)}}const O=["text","number","checkbox","textarea"];function Y(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.slug=="string"&&t.slug.length>0&&O.includes(t.type)}function ke(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'),!O.includes(t.type)){const a=t.type!==void 0?`"${String(t.type)}"`:"none";n.push(`invalid type ${a} — valid types: ${O.join(", ")}`)}return`${typeof t.slug=="string"&&t.slug.length>0?`Field "${t.slug}"`:"Field"}: ${n.join("; ")}.`}function Ce(e){E={};for(const t of e)Y(t)&&(E[t.slug]=t.default_value??"")}function Le(e,t){if(g.innerHTML="",t){const n=document.createElement("div");n.className="field-error",n.textContent=`template.json parse error: ${t}`,g.appendChild(n);return}if(e.length===0){g.innerHTML='<p class="no-fields">No fields in template.json.</p>';return}for(const n of e){if(!Y(n)){const o=document.createElement("div");o.className="field-error",o.textContent=ke(n),g.appendChild(o);continue}const l=n;if(l.type==="checkbox"){const o=document.createElement("div");o.className="field-checkbox-row";const i=document.createElement("input");i.type="checkbox",i.id=`field-${l.slug}`,i.checked=l.default_value==="true"||l.default_value==="1",i.addEventListener("change",()=>{E[l.slug]=i.checked?"true":"false",p()});const r=document.createElement("label");r.className="field-label",r.htmlFor=`field-${l.slug}`,r.textContent=l.label||l.slug,o.appendChild(i),o.appendChild(r),g.appendChild(o);continue}const a=document.createElement("div");a.className="field-group";const s=document.createElement("label");if(s.className="field-label",s.htmlFor=`field-${l.slug}`,s.textContent=l.label||l.slug,a.appendChild(s),l.type==="textarea"){const o=document.createElement("textarea");o.className="field-input",o.id=`field-${l.slug}`,o.rows=3,o.value=l.default_value??"",o.addEventListener("input",()=>{E[l.slug]=o.value,p()}),a.appendChild(o)}else{const o=document.createElement("input");o.type=l.type==="number"?"number":"text",o.className="field-input",o.id=`field-${l.slug}`,o.value=l.default_value??"",o.addEventListener("input",()=>{E[l.slug]=o.value,p()}),a.appendChild(o)}g.appendChild(a)}}function Ie(){const e=new URLSearchParams;e.set("slug",d),e.set("tab",N),H&&e.set("sdk",H),F||e.set("blockCookies","false");for(const[t,n]of Object.entries(E))e.set(`f_${t}`,n);return`/preview?${e.toString()}`}function p(){if(!L||!d)return;const e=Ie();T=e;const t=ne(y),n=t===0;if(f){const i=f.parentElement,r=i.parentElement,ee=r.parentElement;ee.className=n?"preview-stage":"preview-stage preview-stage--breakpoint",r.style.width=n?"100%":"",i.style.width=n?"100%":`${t}px`,f.style.width=n?"100%":`${t}px`,f.style.maxHeight=y==="mobile"?"812px":"",f.src=e;return}m.innerHTML="";const l=document.createElement("div");l.className=n?"preview-stage":"preview-stage preview-stage--breakpoint";const a=document.createElement("div");a.className="preview-scaler";const s=document.createElement("div");s.className="preview-frame-wrapper";const o=document.createElement("iframe");o.style.height="calc(100vh - 48px)",n?(a.style.width="100%",s.style.width="100%",o.style.width="100%"):(s.style.width=`${t}px`,o.style.width=`${t}px`,y==="mobile"&&(o.style.maxHeight="812px")),o.src=e,s.appendChild(o),a.appendChild(s),l.appendChild(a),m.appendChild(l),f=o}async function U(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();L=n,j.textContent=`Template: ${n.name}`,j.style.opacity="1",z(!0),Ce(n.fields),Le(n.fields,n.fieldsParseError),p()}catch(t){console.error("[allegro-preview] Failed to load template:",t),m.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}".`,m.appendChild(n)}}async function xe(){const t=await(await fetch("/api/config")).json();u=t.templates,pe(t.tenant??"");const n=Ee(),l=n&&u.find(a=>a.slug===n)?n:null;l?(d=l,I(),await U(l)):K()}function Se(){const e=localStorage.getItem("allegro-preview-tab"),t=localStorage.getItem("allegro-preview-device");e&&["standalone","append","cut"].includes(e)&&(N=e,$.querySelectorAll(".tab-btn").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)})),t&&["full","desktop","tablet","mobile"].includes(t)&&(y=t,M.querySelectorAll(".device-btn").forEach(n=>{n.classList.toggle("active",n.dataset.device===t)}))}function Q(){const e=new EventSource("/api/events");e.addEventListener("reload",()=>{d&&U(d)}),e.addEventListener("config",()=>{Be()}),e.onerror=()=>{e.close(),setTimeout(Q,2e3)}}async function Be(){u=(await(await fetch("/api/config")).json()).templates,I(),d&&!u.find(n=>n.slug===d)&&K()}function _(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const Ne=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;function Te(){c.value="",v.textContent="",c.classList.remove("invalid"),w.disabled=!1,k.classList.remove("hidden"),c.focus()}function A(){k.classList.add("hidden")}function X(e){return e?Ne.test(e)?null:"Use lowercase letters, numbers, and hyphens only (e.g. my-template).":"Slug is required."}J.addEventListener("click",()=>{T&&window.open(T,"_blank")});se.addEventListener("click",Te);ie.addEventListener("click",A);k.addEventListener("click",e=>{e.target===k&&A()});document.addEventListener("keydown",e=>{e.key==="Escape"&&!k.classList.contains("hidden")&&A()});c.addEventListener("input",()=>{const e=X(c.value);e?(c.classList.add("invalid"),v.textContent=e):(c.classList.remove("invalid"),v.textContent="")});c.addEventListener("keydown",e=>{e.key==="Enter"&&Z()});w.addEventListener("click",()=>void Z());async function Z(){const e=c.value.trim(),t=X(e);if(t){c.classList.add("invalid"),v.textContent=t,c.focus();return}w.disabled=!0,v.textContent="";try{const n=await fetch("/api/template/create",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({slug:e})}),l=await n.json();if(!n.ok){v.textContent=l.error??"Failed to create template.",w.disabled=!1;return}A(),u=[...u,{slug:e,name:e}],I(),await D(e)}catch{v.textContent="Network error. Please try again.",w.disabled=!1}}ue();Se();ye();xe();Q();
|