|
1 | 1 | (function () { |
2 | 2 | 'use strict'; |
3 | 3 |
|
4 | | - var LS_KEY = 'dtd_projects'; |
| 4 | + /* Shared helpers (/js/shared.js): 0.1.0 output, old-key migration, safe embedding */ |
| 5 | + var buildHTML = DTD.buildHTML; |
| 6 | + var loadProjects = DTD.loadProjects; |
| 7 | + var saveProjects = DTD.saveProjects; |
| 8 | + var esc = DTD.esc; |
5 | 9 |
|
6 | | - function loadProjects() { |
7 | | - try { return JSON.parse(localStorage.getItem(LS_KEY) || '[]'); } catch (e) { return []; } |
8 | | - } |
9 | | - function saveProjects(arr) { |
10 | | - try { localStorage.setItem(LS_KEY, JSON.stringify(arr)); } catch (e) {} |
11 | | - } |
12 | 10 | function fmt(ts) { |
13 | 11 | var d = new Date(ts); |
14 | 12 | return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) + |
15 | 13 | ' · ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); |
16 | 14 | } |
17 | | - function esc(s) { |
18 | | - return String(s || '').replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>'); |
19 | | - } |
20 | | - function buildHTML(c) { |
21 | | - 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||'Page') + '</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) + ');<\/script>\n</body>\n</html>'; |
22 | | - } |
23 | 15 |
|
24 | 16 | /* ── THEME ── */ |
25 | 17 | document.getElementById('btn-dark').addEventListener('click', function () { |
|
41 | 33 | var modalEditBtn = document.getElementById('modal-edit-btn'); |
42 | 34 | var modalExportBtn = document.getElementById('modal-export-btn'); |
43 | 35 | var _modalCfg = null; |
44 | | - var _modalId = null; |
45 | 36 |
|
46 | 37 | function openModal(id, name, cfg) { |
47 | | - _modalCfg = cfg; _modalId = id; |
| 38 | + _modalCfg = cfg; |
48 | 39 | modalTitle.textContent = name; |
49 | 40 | modalFrame.srcdoc = buildHTML(cfg); |
50 | 41 | modalEditBtn.href = '/builder?id=' + id; |
|
59 | 50 | }); |
60 | 51 | modalExportBtn.addEventListener('click', function () { |
61 | 52 | if (!_modalCfg) return; |
62 | | - var html = buildHTML(_modalCfg); |
63 | | - var name = (_modalCfg.logoName||'page').toLowerCase().replace(/\s+/g,'-'); |
64 | | - var blob = new Blob([html],{type:'text/html'}); |
| 53 | + var name = (_modalCfg.logoName || 'page').toLowerCase().replace(/\s+/g, '-'); |
| 54 | + var blob = new Blob([buildHTML(_modalCfg)], { type: 'text/html' }); |
65 | 55 | var a = document.createElement('a'); |
66 | | - a.href = URL.createObjectURL(blob); a.download = name+'.html'; a.click(); |
| 56 | + a.href = URL.createObjectURL(blob); a.download = name + '.html'; a.click(); |
67 | 57 | URL.revokeObjectURL(a.href); |
68 | 58 | }); |
69 | 59 | document.addEventListener('keydown', function (e) { |
70 | 60 | if (e.key === 'Escape' && previewModal.classList.contains('open')) modalClose.click(); |
71 | 61 | }); |
72 | 62 |
|
73 | 63 | /* ── RENDER ── */ |
74 | | - var grid = document.getElementById('projects-grid'); |
75 | | - var emptyState = document.getElementById('empty-state'); |
76 | | - var countBadge = document.getElementById('count-badge'); |
77 | | - var searchInput= document.getElementById('search-input'); |
| 64 | + var grid = document.getElementById('projects-grid'); |
| 65 | + var emptyState = document.getElementById('empty-state'); |
| 66 | + var countBadge = document.getElementById('count-badge'); |
| 67 | + var searchInput = document.getElementById('search-input'); |
78 | 68 |
|
79 | 69 | function render(filter) { |
80 | 70 | var list = loadProjects(); |
81 | 71 | if (filter) { |
82 | 72 | var f = filter.toLowerCase(); |
83 | 73 | list = list.filter(function (p) { return p.name.toLowerCase().includes(f); }); |
84 | 74 | } |
85 | | - countBadge.textContent = loadProjects().length; // always real count |
| 75 | + countBadge.textContent = loadProjects().length; // always the real count |
86 | 76 |
|
87 | 77 | if (list.length === 0) { |
88 | 78 | emptyState.style.display = 'flex'; |
|
91 | 81 | } |
92 | 82 | emptyState.style.display = 'none'; |
93 | 83 |
|
94 | | - // Sort newest first |
95 | | - list = list.slice().sort(function (a, b) { return b.updatedAt - a.updatedAt; }); |
| 84 | + list = list.slice().sort(function (a, b) { return b.updatedAt - a.updatedAt; }); // newest first |
96 | 85 |
|
97 | 86 | grid.innerHTML = list.map(function (p) { |
98 | | - return '<div class="proj-card" data-id="' + p.id + '">' + |
99 | | - '<div class="proj-thumb" data-preview-id="' + p.id + '">' + |
100 | | - '<iframe srcdoc="' + esc(buildHTML(p.config)) + '" sandbox="allow-scripts allow-same-origin"></iframe>' + |
101 | | - '<div class="proj-thumb-overlay"><button class="ov-btn" data-preview-id="' + p.id + '"><i class="fas fa-eye"></i> Preview</button></div>' + |
| 87 | + return '<div class="proj-card" data-id="' + esc(p.id) + '">' + |
| 88 | + '<div class="proj-thumb" data-preview-id="' + esc(p.id) + '">' + |
| 89 | + '<iframe srcdoc="' + esc(buildHTML(p.config, true)) + '" sandbox="allow-scripts allow-same-origin" loading="lazy" title="' + esc(p.name) + '"></iframe>' + |
| 90 | + '<div class="proj-thumb-overlay"><button class="ov-btn" data-preview-id="' + esc(p.id) + '"><i class="fas fa-eye"></i> Preview</button></div>' + |
102 | 91 | '</div>' + |
103 | 92 | '<div class="proj-body">' + |
104 | 93 | '<div class="proj-name-row">' + |
105 | | - '<span class="proj-name" data-rename-id="' + p.id + '">' + esc(p.name) + '</span>' + |
106 | | - '<input class="proj-name-input" type="text" value="' + esc(p.name) + '" data-rename-input="' + p.id + '" maxlength="60">' + |
107 | | - '<span class="proj-tpl">' + esc(p.config.template||'figsh') + '</span>' + |
| 94 | + '<span class="proj-name" data-rename-id="' + esc(p.id) + '">' + esc(p.name) + '</span>' + |
| 95 | + '<input class="proj-name-input" type="text" value="' + esc(p.name) + '" data-rename-input="' + esc(p.id) + '" maxlength="60">' + |
| 96 | + '<span class="proj-tpl">' + esc(p.config.template || 'figsh') + '</span>' + |
108 | 97 | '</div>' + |
109 | 98 | '<div class="proj-meta">' + fmt(p.updatedAt) + '</div>' + |
110 | 99 | '<div class="proj-actions">' + |
111 | | - '<a class="pa-btn" href="/builder?id=' + p.id + '"><i class="fas fa-pen"></i> Edit</a>' + |
112 | | - '<button class="pa-btn" data-preview-id="' + p.id + '"><i class="fas fa-eye"></i> Preview</button>' + |
113 | | - '<button class="pa-btn danger" data-delete-id="' + p.id + '"><i class="fas fa-trash"></i> Delete</button>' + |
| 100 | + '<a class="pa-btn" href="/builder?id=' + esc(p.id) + '"><i class="fas fa-pen"></i> Edit</a>' + |
| 101 | + '<button class="pa-btn" data-preview-id="' + esc(p.id) + '"><i class="fas fa-eye"></i> Preview</button>' + |
| 102 | + '<button class="pa-btn danger" data-delete-id="' + esc(p.id) + '"><i class="fas fa-trash"></i> Delete</button>' + |
114 | 103 | '</div>' + |
115 | 104 | '</div>' + |
116 | 105 | '</div>'; |
|
128 | 117 | function commit() { |
129 | 118 | var val = inp.value.trim(); if (!val) { render(searchInput.value); return; } |
130 | 119 | var id = inp.dataset.renameInput; |
131 | | - var all = loadProjects(); var p = all.find(function(x){return x.id===id;}); |
| 120 | + var all = loadProjects(); var p = all.find(function (x) { return x.id === id; }); |
132 | 121 | if (p) { p.name = val; saveProjects(all); } |
133 | 122 | render(searchInput.value); |
134 | 123 | } |
|
144 | 133 | el.addEventListener('click', function (e) { |
145 | 134 | e.preventDefault(); |
146 | 135 | var id = el.dataset.previewId; |
147 | | - var all = loadProjects(); var p = all.find(function(x){return x.id===id;}); |
| 136 | + var p = loadProjects().find(function (x) { return x.id === id; }); |
148 | 137 | if (p) openModal(p.id, p.name, p.config); |
149 | 138 | }); |
150 | 139 | }); |
|
154 | 143 | btn.addEventListener('click', function () { |
155 | 144 | if (!confirm('Delete "' + btn.closest('.proj-card').querySelector('.proj-name').textContent + '"?')) return; |
156 | 145 | var id = btn.dataset.deleteId; |
157 | | - saveProjects(loadProjects().filter(function(x){return x.id!==id;})); |
| 146 | + saveProjects(loadProjects().filter(function (x) { return x.id !== id; })); |
158 | 147 | render(searchInput.value); |
159 | 148 | }); |
160 | 149 | }); |
|
164 | 153 | render(); |
165 | 154 |
|
166 | 155 | })(); |
| 156 | + |
0 commit comments