Skip to content

Commit fb0c0e8

Browse files
authored
Refactor builder.js for improved readability and structure
1 parent fe86d59 commit fb0c0e8

1 file changed

Lines changed: 72 additions & 76 deletions

File tree

‎js/builder.js‎

Lines changed: 72 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
(function () {
22
'use strict';
33

4+
/* Shared helpers (/js/shared.js): 0.1.0 output, old-key migration, safe embedding */
5+
var buildHTML = DTD.buildHTML;
6+
47
/* ── STATE ────────────────────────────────────────────────────── */
58
var cfg = null; // active config object
69
var activeId = null; // localStorage project id being edited (null = unsaved)
@@ -23,6 +26,7 @@
2326
var saveBtn = document.getElementById('save-btn');
2427
var copyBtn = document.getElementById('copy-btn');
2528
var exportBtn = document.getElementById('export-btn');
29+
var deviceBtn = document.getElementById('device-btn');
2630

2731
var projCount = document.getElementById('proj-count');
2832

@@ -48,16 +52,10 @@
4852
function uid() {
4953
return 'p' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
5054
}
51-
function fmt(ts) {
52-
var d = new Date(ts);
53-
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) +
54-
' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
55-
}
5655

5756
/* ── PROJECTS UI ──────────────────────────────────────────────── */
5857
function updateProjCount() {
59-
var n = loadProjects().length;
60-
projCount.textContent = n;
58+
projCount.textContent = loadProjects().length;
6159
}
6260

6361
/* ── SAVE FLOW ────────────────────────────────────────────────── */
@@ -116,8 +114,7 @@
116114
modalTitle.textContent = name || 'Preview';
117115
modalFrame.srcdoc = buildHTML(c);
118116
previewModal.classList.add('open');
119-
// store config for export
120-
modalFrame._cfg = c;
117+
modalFrame._cfg = c; // store config for export
121118
}
122119
modalClose.addEventListener('click', function () {
123120
previewModal.classList.remove('open');
@@ -126,25 +123,26 @@
126123
modalExport.addEventListener('click', function () {
127124
var c = modalFrame._cfg;
128125
if (!c) return;
129-
var html = buildHTML(c);
130-
var name = (c.logoName || 'page').toLowerCase().replace(/\s+/g, '-');
131-
var blob = new Blob([html], { type: 'text/html' });
132-
var a = document.createElement('a');
133-
a.href = URL.createObjectURL(blob); a.download = name + '.html'; a.click();
134-
URL.revokeObjectURL(a.href);
126+
download(buildHTML(c), c.logoName);
135127
});
136-
// close modal on backdrop click
137128
previewModal.addEventListener('click', function (e) {
138129
if (e.target === previewModal) modalClose.click();
139130
});
140-
// ESC closes modal or dialog
141131
document.addEventListener('keydown', function (e) {
142132
if (e.key === 'Escape') {
143133
if (previewModal.classList.contains('open')) modalClose.click();
144134
if (saveDialog.classList.contains('open')) saveDialog.classList.remove('open');
145135
}
146136
});
147137

138+
function download(html, logoName) {
139+
var name = (logoName || 'page').toLowerCase().replace(/\s+/g, '-');
140+
var blob = new Blob([html], { type: 'text/html' });
141+
var a = document.createElement('a');
142+
a.href = URL.createObjectURL(blob); a.download = name + '.html'; a.click();
143+
URL.revokeObjectURL(a.href);
144+
}
145+
148146
/* ── THEME ────────────────────────────────────────────────────── */
149147
document.getElementById('btn-dark').addEventListener('click', function () {
150148
document.documentElement.removeAttribute('data-theme');
@@ -183,17 +181,32 @@
183181
});
184182

185183
/* ── PREVIEW ──────────────────────────────────────────────────── */
184+
var frameLive = false;
185+
previewFrame.addEventListener('load', function () { frameLive = true; });
186+
186187
function schedulePreview() {
187188
clearTimeout(previewTimer);
188189
previewTimer = setTimeout(doPreview, 280);
189190
}
191+
// First call loads the page; later calls update it in place (keeps scroll, no flicker)
190192
function doPreview() {
191193
if (!cfg) return;
192-
previewFrame.srcdoc = buildHTML(cfg);
193194
previewEmpty.style.display = 'none';
194195
previewFrame.style.display = 'block';
196+
if (frameLive && previewFrame.contentWindow) {
197+
previewFrame.contentWindow.postMessage({ dtdCfg: DTD.migrate(cfg) }, '*');
198+
} else {
199+
previewFrame.srcdoc = buildHTML(cfg, true);
200+
}
195201
}
196202

203+
if (deviceBtn) deviceBtn.addEventListener('click', function () {
204+
var m = previewFrame.classList.toggle('m');
205+
deviceBtn.innerHTML = m
206+
? '<i class="fas fa-desktop"></i> Desktop'
207+
: '<i class="fas fa-mobile-screen"></i> Mobile';
208+
});
209+
197210
/* ── WIRE SIMPLE INPUTS (once) ────────────────────────────────── */
198211
function wireSimpleInputs() {
199212
function w(id, path) {
@@ -211,6 +224,7 @@
211224
schedulePreview();
212225
});
213226
}
227+
// Old key names are kept on purpose: DTD.buildHTML maps them to 0.1.0 names
214228
w('e-logoName', 'logoName');
215229
w('e-logoUrl', 'logo');
216230
w('e-heroTag', 'heroTag');
@@ -247,38 +261,37 @@
247261
}
248262

249263
/* ── HTML ESCAPE ──────────────────────────────────────────────── */
250-
function esc(s) {
251-
return String(s || '').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
252-
}
264+
var esc = DTD.esc;
253265

254266
/* ── RENDER LISTS ─────────────────────────────────────────────── */
255267
function renderNavLinks() {
256-
var el = document.getElementById('navLinksList');
257-
if (!el || !cfg) return;
258-
el.innerHTML = (cfg.navLinks || []).map(function (lnk, i) {
259-
return '<div class="list-item"><div class="li-row">' +
260-
'<input type="text" value="' + esc(lnk.label) + '" data-nll="' + i + '" placeholder="Label">' +
261-
'<input type="text" value="' + esc(lnk.href) + '" data-nlh="' + i + '" placeholder="#section">' +
262-
'<button class="btn-rm" data-rnl="' + i + '" title="Remove"><i class="fas fa-times"></i></button>' +
263-
'</div></div>';
264-
}).join('');
265-
el.querySelectorAll('[data-nll]').forEach(function (inp) {
266-
inp.addEventListener('input', function () { cfg.navLinks[+inp.dataset.nll].label = inp.value; schedulePreview(); });
267-
});
268-
el.querySelectorAll('[data-nlh]').forEach(function (inp) {
269-
inp.addEventListener('input', function () { cfg.navLinks[+inp.dataset.nlh].href = inp.value; schedulePreview(); });
270-
});
271-
el.querySelectorAll('[data-rnl]').forEach(function (btn) {
272-
btn.addEventListener('click', function () { cfg.navLinks.splice(+btn.dataset.rnl, 1); renderNavLinks(); schedulePreview(); });
273-
});
274-
}
275-
268+
var el = document.getElementById('navLinksList');
269+
if (!el || !cfg) return;
270+
el.innerHTML = (cfg.navLinks || []).map(function (lnk, i) {
271+
return '<div class="list-item"><div class="li-row">' +
272+
'<input type="text" value="' + esc(lnk.label) + '" data-nll="' + i + '" placeholder="Label">' +
273+
'<input type="text" value="' + esc(lnk.href) + '" data-nlh="' + i + '" placeholder="#section">' +
274+
'<button class="btn-rm" data-rnl="' + i + '" title="Remove"><i class="fas fa-times"></i></button>' +
275+
'</div></div>';
276+
}).join('');
277+
el.querySelectorAll('[data-nll]').forEach(function (inp) {
278+
inp.addEventListener('input', function () { cfg.navLinks[+inp.dataset.nll].label = inp.value; schedulePreview(); });
279+
});
280+
el.querySelectorAll('[data-nlh]').forEach(function (inp) {
281+
inp.addEventListener('input', function () { cfg.navLinks[+inp.dataset.nlh].href = inp.value; schedulePreview(); });
282+
});
283+
el.querySelectorAll('[data-rnl]').forEach(function (btn) {
284+
btn.addEventListener('click', function () { cfg.navLinks.splice(+btn.dataset.rnl, 1); renderNavLinks(); schedulePreview(); });
285+
});
286+
}
287+
276288
document.getElementById('addNavLinkBtn').addEventListener('click', function () {
277-
if (!cfg) return;
278-
cfg.navLinks.push({ label: 'New link', href: '#' });
279-
renderNavLinks();
280-
schedulePreview();
281-
});
289+
if (!cfg) return;
290+
if (!cfg.navLinks) cfg.navLinks = [];
291+
cfg.navLinks.push({ label: 'New link', href: '#' });
292+
renderNavLinks();
293+
schedulePreview();
294+
});
282295

283296
function renderBrands() {
284297
var el = document.getElementById('brandsList');
@@ -301,7 +314,6 @@
301314
'<div class="li-row"><input type="text" value="' + esc(f.icon) + '" data-fi="' + i + '" placeholder="Icon HTML"></div>' +
302315
'</div>';
303316
}).join('');
304-
305317
el.querySelectorAll('[data-ft]').forEach(function (inp) { inp.addEventListener('input', function () { cfg.features[+inp.dataset.ft].title = inp.value; schedulePreview(); }); });
306318
el.querySelectorAll('[data-fd]').forEach(function (inp) { inp.addEventListener('input', function () { cfg.features[+inp.dataset.fd].desc = inp.value; schedulePreview(); }); });
307319
el.querySelectorAll('[data-fi]').forEach(function (inp) { inp.addEventListener('input', function () { cfg.features[+inp.dataset.fi].icon = inp.value; schedulePreview(); }); });
@@ -380,17 +392,15 @@
380392
el.querySelectorAll('[data-rl]').forEach(function (btn) { btn.addEventListener('click', function () { cfg.footer.legal.splice(+btn.dataset.rl,1); renderLegal(); schedulePreview(); }); });
381393
}
382394

383-
384-
385395
function renderAllLists() {
386-
renderNavLinks(); // added
387-
renderBrands();
388-
renderFeatures();
389-
renderPanels();
390-
renderFooterCols();
391-
renderSocial();
392-
renderLegal();
393-
}
396+
renderNavLinks();
397+
renderBrands();
398+
renderFeatures();
399+
renderPanels();
400+
renderFooterCols();
401+
renderSocial();
402+
renderLegal();
403+
}
394404

395405
/* ── ADD BUTTONS ──────────────────────────────────────────────── */
396406
document.getElementById('addBrandBtn').addEventListener('click', function () { if (!cfg) return; cfg.brands.push('New Brand'); renderBrands(); schedulePreview(); });
@@ -488,7 +498,8 @@
488498
}
489499
return {
490500
template: tmpl, logo: 'https://fscss.devtem.org/assets/images/fscss.png', logoName: brand,
491-
navLinks: [{ label: 'Work', href: '#work' }, { label: 'Services', href: '#services' }, { label: 'About', href: '#about' }, { label: 'Contact', href: '#contact' }],
501+
// 0.1.0 anchors: the features section renders as #features
502+
navLinks: [{ label: 'Features', href: '#features' }, { label: 'Contact', href: '#' }],
492503
ctaText: 'Get started', heroTag: heroTag, heroTitleLine1: heroLine1, heroTitleLine2: heroLine2,
493504
heroSubtitle: short, primaryBtnText: 'Learn more', secondaryBtnText: 'Contact us',
494505
brands: brands, brandsSubtitle: 'technologies & platforms I work with daily',
@@ -507,11 +518,6 @@
507518
};
508519
}
509520

510-
/* ── BUILD HTML ───────────────────────────────────────────────── */
511-
function buildHTML(c) {
512-
return '<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1">\n <title>' + (c.logoName || 'devtem') + '</title>\n <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/devtemdesign@0.0.2/style.css">\n <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">\n <script src="https://cdn.jsdelivr.net/npm/devtemdesign@0.0.2/script.js"><\/script>\n</head>\n<body>\n <div id="app"></div>\n <script>devtemDesign("app", ' + JSON.stringify(c, null, 2) + ');<\/script>\n</body>\n</html>';
513-
}
514-
515521
/* ── GENERATE ─────────────────────────────────────────────────── */
516522
generateBtn.addEventListener('click', function () {
517523
var desc = descEl.value.trim();
@@ -551,12 +557,7 @@
551557
/* ── EXPORT ───────────────────────────────────────────────────── */
552558
exportBtn.addEventListener('click', function () {
553559
if (!cfg) return;
554-
var html = buildHTML(cfg);
555-
var name = (cfg.logoName || 'page').toLowerCase().replace(/\s+/g, '-');
556-
var blob = new Blob([html], { type: 'text/html' });
557-
var a = document.createElement('a');
558-
a.href = URL.createObjectURL(blob); a.download = name + '.html'; a.click();
559-
URL.revokeObjectURL(a.href);
560+
download(buildHTML(cfg), cfg.logoName);
560561
});
561562

562563
/* ── INIT: URL PARAM LOAD ─────────────────────────────────────── */
@@ -565,24 +566,19 @@
565566
var params = new URLSearchParams(window.location.search);
566567
var id = params.get('id');
567568
if (!id) return;
568-
var list = loadProjects();
569-
var p = list.find(function (x) { return x.id === id; });
569+
var p = loadProjects().find(function (x) { return x.id === id; });
570570
if (!p) return;
571-
// Load the project into editor
572571
activeId = p.id;
573572
cfg = JSON.parse(JSON.stringify(p.config));
574573
template = cfg.template || 'figsh';
575-
// Sync template buttons
576574
document.querySelectorAll('.tpl-btn').forEach(function (b) {
577575
b.classList.toggle('active', b.dataset.tpl === template);
578576
});
579-
// Update generate btn label
580577
generateBtn.innerHTML = '<i class="fas fa-rotate"></i> Regenerate';
581578
generateBtn.disabled = false;
582-
// Open editor
583579
openEditor(true);
584-
// Update browser title
585580
document.title = 'Editing: ' + p.name + ' · devtemDesign';
586581
}());
587582

588583
})();
584+

0 commit comments

Comments
 (0)