create-gardener 1.1.3 → 1.1.4
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/package.json +1 -1
- package/template/package.json +1 -1
- package/template/src/backend/routes/gardener.route.ts +1 -0
- package/template/src/frontend/components/emailsvg.js +55 -0
- package/template/src/frontend/components/eyeoff.js +50 -0
- package/template/src/frontend/components/eyeon.js +44 -0
- package/template/src/frontend/components/nonui/api.js +22 -0
- package/template/src/frontend/components/notification.js +67 -0
- package/template/src/frontend/components/passwordBox.js +105 -0
- package/template/src/frontend/frontendtemplate.ejs +12 -5
- package/template/src/frontend/gardener.js +1 -4
- package/template/src/frontend/style2.css +1 -0
- package/template/src/frontend/views/_.ejs +9 -1
- package/template/src/frontend/views/_login.ejs +73 -0
- package/template/src/frontendStatic/cache/gardener_500x500.webp +0 -0
- package/template/src/frontendStatic/components/test.js +0 -54
- package/template/src/frontendStatic/gardener.js +0 -404
- package/template/src/frontendStatic/global.js +0 -57
- package/template/src/frontendStatic/index.html +0 -216
- package/template/src/frontendStatic/style.css +0 -1220
- package/template/src/frontendStatic/tailwind.css +0 -1
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
const config = {
|
|
2
|
-
mode: 'dev',
|
|
3
|
-
componentdir: 'components',
|
|
4
|
-
hotreload: true
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
let hotReloadtimeout;
|
|
8
|
-
const body = fetchElement('body');
|
|
9
|
-
let hotReload = localStorage.getItem('hotreload');
|
|
10
|
-
|
|
11
|
-
if (hotReload === null) hotReload = config.hotreload;
|
|
12
|
-
else if (hotReload === 'true') hotReload = true
|
|
13
|
-
else if (hotReload === 'false') hotReload = false
|
|
14
|
-
|
|
15
|
-
function opnPagedialog(btn = true) {
|
|
16
|
-
if (btn) {
|
|
17
|
-
const dialog = gardener({
|
|
18
|
-
t: 'form', cn: ['addpageform', 'fixed', 'left-2/5', 'bg-gray-200', 'rounded-lg', 'block', 'top-2/5', 'p-2', 'flex', 'flex-col', 'p-5', 'gap-2'], events: {
|
|
19
|
-
submit: async (e) => {
|
|
20
|
-
try {
|
|
21
|
-
e.preventDefault()
|
|
22
|
-
const data = new FormData(e.target);
|
|
23
|
-
const input = Object.fromEntries(data.entries());
|
|
24
|
-
console.log(input)
|
|
25
|
-
|
|
26
|
-
const response = await fetch('/addpage', {
|
|
27
|
-
method: 'POST',
|
|
28
|
-
headers: {
|
|
29
|
-
"Content-Type": 'application/json'
|
|
30
|
-
},
|
|
31
|
-
body: JSON.stringify(input)
|
|
32
|
-
}).then(res => res.json())
|
|
33
|
-
console.log(response)
|
|
34
|
-
opnPagedialog(false)
|
|
35
|
-
window.location.href = input.page
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
console.log(err)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
}, children: [{
|
|
43
|
-
t: 'label',
|
|
44
|
-
txt: 'ENTER PATH FOR NEW PAGE'
|
|
45
|
-
}, { t: 'input', attr: { name: 'page' }, cn: ['pathinput'] }]
|
|
46
|
-
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
console.log('test')
|
|
50
|
-
appendElement(body, dialog);
|
|
51
|
-
fetchElement('.pathinput').focus();
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
console.log('removed')
|
|
55
|
-
fetchElement('.addpageform').remove();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (config.mode === 'dev') {
|
|
60
|
-
const addPagebtn = gardener({
|
|
61
|
-
t: 'button',
|
|
62
|
-
cn: ['pb-1.5', 'flex', 'items-center', 'justify-center', 'h-15', 'w-15', 'bg-green-300', 'fixed', 'bottom-22', 'right-2', 'rounded-full', 'text-5xl'],
|
|
63
|
-
children: [{ t: 'span', txt: '+' }],
|
|
64
|
-
events: {
|
|
65
|
-
click: opnPagedialog
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
appendElement(body, addPagebtn);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
appendElement(body, gardener({
|
|
73
|
-
t: 'p',
|
|
74
|
-
cn: ['bg-gray-200', 'fixed', 'bottom-0', 'z-100', 'right-0', 'border-b-1', 'p-2', 'rounded-md'],
|
|
75
|
-
children: [
|
|
76
|
-
{
|
|
77
|
-
t: 'span',
|
|
78
|
-
txt: 'Press '
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
t: 'span',
|
|
82
|
-
cn: ['text-green-500', 'font-bold'],
|
|
83
|
-
txt: 'Ctrl+h'
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
t: 'span',
|
|
87
|
-
txt: ' to toggle Hot Reload'
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
t: 'form',
|
|
91
|
-
attr: {
|
|
92
|
-
id: 'hrcheckbox',
|
|
93
|
-
},
|
|
94
|
-
events: {
|
|
95
|
-
click: () => togglehotreload()
|
|
96
|
-
},
|
|
97
|
-
cn: ['p-2', 'bg-red-300'],
|
|
98
|
-
children: [{
|
|
99
|
-
t: 'label',
|
|
100
|
-
txt: 'Hot Reload ',
|
|
101
|
-
}
|
|
102
|
-
, {
|
|
103
|
-
t: 'input',
|
|
104
|
-
cn: ['hrcheckbox'],
|
|
105
|
-
attr: {
|
|
106
|
-
type: 'checkbox'
|
|
107
|
-
}
|
|
108
|
-
}]
|
|
109
|
-
}
|
|
110
|
-
]
|
|
111
|
-
}))
|
|
112
|
-
|
|
113
|
-
//appendElement(body, gardener())
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
togglehotreload();
|
|
117
|
-
document.addEventListener('keydown', function(e) {
|
|
118
|
-
// Detect Ctrl + H
|
|
119
|
-
if (e.ctrlKey && e.key.toLowerCase() === 'h') {
|
|
120
|
-
e.preventDefault(); // Stop browser from opening history
|
|
121
|
-
// Your logic here...
|
|
122
|
-
togglehotreload();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
//if (config.mode === 'dev') {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
function togglehotreload() {
|
|
131
|
-
const hr = hotReload;
|
|
132
|
-
const hrcheck = fetchElement('#hrcheckbox');
|
|
133
|
-
|
|
134
|
-
localStorage.setItem('hotreload', hr);
|
|
135
|
-
|
|
136
|
-
hotReload = !hotReload;
|
|
137
|
-
|
|
138
|
-
if (hr) {
|
|
139
|
-
hrcheck.style.background = '#66e666';
|
|
140
|
-
fetchElement('.hrcheckbox').checked = true;
|
|
141
|
-
localStorage.setItem('hotreload', 'true');
|
|
142
|
-
hotReloadtimeout = setTimeout(() => window.location.reload(), 1000);
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
hrcheck.style.background = 'red';
|
|
146
|
-
fetchElement('.hrcheckbox').checked = false;
|
|
147
|
-
localStorage.setItem('hotreload', 'false');
|
|
148
|
-
clearTimeout(hotReloadtimeout);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
//localStorage.setItem('hotreload', hotReload);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function parserWindow(text) {
|
|
155
|
-
if (config.mode !== 'dev') return;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const result = gardener({
|
|
159
|
-
t: 'div',
|
|
160
|
-
cn: ['fixed', 'border-2', 'border-black', 'bg-gray-500', 'text-white', 'rounded-lg', 'z-90', 'w-2/4', 'h-2/4', 'left-1/4', 'flex', 'flex-col', 'justify-between', 'top-1/4'],
|
|
161
|
-
children: [
|
|
162
|
-
{
|
|
163
|
-
t: 'div',
|
|
164
|
-
cn: ['bg-gray-200', 'h-15', 'text-black', 'rounded-t-lg', 'flex', 'items-center', 'justify-around'],
|
|
165
|
-
children: [
|
|
166
|
-
{
|
|
167
|
-
t: 'h3',
|
|
168
|
-
cn: ['font-bold'],
|
|
169
|
-
txt: 'Parser Window'
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
t: 'button',
|
|
173
|
-
cn: ['p-2', 'bg-red-300', 'rounded-lg', 'cursor-pointer'],
|
|
174
|
-
txt: 'Add Component',
|
|
175
|
-
attr: {
|
|
176
|
-
id: 'copybtn'
|
|
177
|
-
},
|
|
178
|
-
events: {
|
|
179
|
-
click: copytxt
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
]
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
t: 'p',
|
|
186
|
-
cn: ['p-5', 'overflow-scroll'],
|
|
187
|
-
txt: text
|
|
188
|
-
},
|
|
189
|
-
]
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
function copytxt() {
|
|
193
|
-
result.remove()
|
|
194
|
-
|
|
195
|
-
const compform = gardener({
|
|
196
|
-
t: 'form',
|
|
197
|
-
events: {
|
|
198
|
-
submit: (event) => {
|
|
199
|
-
event.preventDefault()
|
|
200
|
-
copyText(text, `${fetchElement('.componentInp').value}.js`)
|
|
201
|
-
compform.remove();
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
cn: ['fixed', 'left-2/5', 'bg-gray-500', 'rounded-lg', 'block', 'top-2/5', 'p-2'],
|
|
205
|
-
children: [
|
|
206
|
-
{
|
|
207
|
-
t: 'input',
|
|
208
|
-
cn: ['bg-white', 'componentInp'],
|
|
209
|
-
attr: {
|
|
210
|
-
type: 'text',
|
|
211
|
-
placeholder: 'Component Name'
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
]
|
|
215
|
-
});
|
|
216
|
-
appendElement(body, compform);
|
|
217
|
-
|
|
218
|
-
fetchElement('.componentInp').focus();
|
|
219
|
-
//setTimeout(() => result.remove(), 500);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
appendElement(body, result);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
async function copyText(txt, path) {
|
|
228
|
-
// await navigator.clipboard.writeText(txt);
|
|
229
|
-
try {
|
|
230
|
-
const res = await fetch('/addcomponent', {
|
|
231
|
-
method: 'POST',
|
|
232
|
-
headers: {
|
|
233
|
-
"Content-Type": 'application/json'
|
|
234
|
-
},
|
|
235
|
-
body: JSON.stringify({ component: txt, path: `${config.componentdir}/${path}` })
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
if (!res.ok) console.error('wrong');
|
|
239
|
-
|
|
240
|
-
const data = await res.json()
|
|
241
|
-
console.log(data);
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
catch (err) {
|
|
245
|
-
console.error(err);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
export function fetchElement(param) {
|
|
254
|
-
return document.querySelector(param);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function appendElement(parent, child) {
|
|
258
|
-
parent.appendChild(child);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export function createElement(type, classname) {
|
|
262
|
-
let element = document.createElement(type);
|
|
263
|
-
if (classname)
|
|
264
|
-
element.classList.add(...classname);
|
|
265
|
-
return element;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export function insertText(element, text) {
|
|
269
|
-
element.innerText = text;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function replaceElement(original, New) {
|
|
273
|
-
original.replaceWith(New);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export function gardener(Dom) {
|
|
277
|
-
|
|
278
|
-
if (Dom.nodeType === 1) return Dom;
|
|
279
|
-
// detect if this is an SVG element
|
|
280
|
-
const isSVG = [
|
|
281
|
-
'svg', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline', 'g', 'defs', 'clipPath', 'use'
|
|
282
|
-
].includes(Dom.t);
|
|
283
|
-
|
|
284
|
-
// create element accordingly
|
|
285
|
-
let element;
|
|
286
|
-
|
|
287
|
-
if (isSVG) {
|
|
288
|
-
element = document.createElementNS('http://www.w3.org/2000/svg', Dom.t);
|
|
289
|
-
if (Dom.cn)
|
|
290
|
-
element.classList.add(...Dom.cn);
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
element = createElement(Dom.t, Dom.cn);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// text content (skip for SVG like <path>)
|
|
297
|
-
if (Dom.txt) {
|
|
298
|
-
insertText(element, Dom.txt);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// apply attributes safely
|
|
302
|
-
const propertyNames = new Set([
|
|
303
|
-
'value', 'selected', 'muted', 'disabled',
|
|
304
|
-
'selectedIndex', 'volume', // etc.
|
|
305
|
-
]);
|
|
306
|
-
|
|
307
|
-
if (Dom.attr) {
|
|
308
|
-
for (const [key, value] of Object.entries(Dom.attr)) {
|
|
309
|
-
if (isSVG || key.startsWith('data-') || key.startsWith('aria-')) {
|
|
310
|
-
element.setAttribute(key, value);
|
|
311
|
-
} else if (key in element && !propertyNames.has(key)) {
|
|
312
|
-
// Prefer property for known safe cases
|
|
313
|
-
try { element[key] = value === '' ? true : value; } catch (e) { element.setAttribute(key, value); }
|
|
314
|
-
} else {
|
|
315
|
-
element.setAttribute(key, value);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (Dom.events) {
|
|
321
|
-
Object.entries(Dom.events).forEach(([eventName, handler]) => {
|
|
322
|
-
element.addEventListener(eventName, handler);
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// recursively handle children
|
|
327
|
-
if (Dom.children) {
|
|
328
|
-
Dom.children.forEach(child => appendElement(element, gardener(child)));
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return element;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export function parser(element, isParent = true) {
|
|
335
|
-
if (typeof element === 'string') {
|
|
336
|
-
// If user passes raw HTML string
|
|
337
|
-
const temp = document.createElement('div');
|
|
338
|
-
temp.innerHTML = element.trim();
|
|
339
|
-
element = temp.firstElementChild;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const obj = {
|
|
343
|
-
t: element.tagName.toLowerCase(),
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
// add classes if present
|
|
347
|
-
if (element.classList.length) {
|
|
348
|
-
obj.cn = Array.from(element.classList);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// add attributes if present
|
|
352
|
-
const attrs = {};
|
|
353
|
-
for (const attr of element.attributes) {
|
|
354
|
-
if (attr.name !== 'class') attrs[attr.name] = attr.value;
|
|
355
|
-
}
|
|
356
|
-
if (Object.keys(attrs).length) obj.attr = attrs;
|
|
357
|
-
// add text content (only if no children)
|
|
358
|
-
if (element.childNodes.length === 1 && element.firstChild.nodeType === Node.TEXT_NODE) {
|
|
359
|
-
obj.txt = element.textContent.trim();
|
|
360
|
-
|
|
361
|
-
if (isParent) {
|
|
362
|
-
parserWindow(JSON.stringify(obj))
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return obj;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// add children recursively
|
|
369
|
-
const children = [];
|
|
370
|
-
for (const child of element.children) {
|
|
371
|
-
children.push(parser(child, false));
|
|
372
|
-
}
|
|
373
|
-
if (children.length) obj.children = children;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (isParent) {
|
|
377
|
-
parserWindow(JSON.stringify(obj))
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return obj
|
|
381
|
-
//Let Browser do the migration from html to json and then use copy paste
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export function imagePreloader(images) {
|
|
385
|
-
const body = fetchElement('body')
|
|
386
|
-
images.forEach(entry => {
|
|
387
|
-
appendElement(body, gardener({
|
|
388
|
-
t: 'img',
|
|
389
|
-
cn: ['preloaderimage'],
|
|
390
|
-
attr: {
|
|
391
|
-
src: entry,
|
|
392
|
-
alt: entry
|
|
393
|
-
}
|
|
394
|
-
}));
|
|
395
|
-
|
|
396
|
-
setTimeout(() => {
|
|
397
|
-
const images = document.querySelectorAll('.preloaderimage');
|
|
398
|
-
images.forEach(entry => { entry.style.display = 'none' });
|
|
399
|
-
}, 0)
|
|
400
|
-
|
|
401
|
-
})
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { parser, fetchElement, replaceElement, gardener, appendElement } from './gardener.js'
|
|
2
|
-
|
|
3
|
-
const body = fetchElement('#body')
|
|
4
|
-
|
|
5
|
-
nextPagehandler();
|
|
6
|
-
pageloader();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function nextPagehandler() {
|
|
10
|
-
const anchor = document.querySelectorAll('a')
|
|
11
|
-
anchor.forEach(link => {
|
|
12
|
-
|
|
13
|
-
link.addEventListener('click', (e) => {
|
|
14
|
-
|
|
15
|
-
//event delegation
|
|
16
|
-
const link = e.target.closest('a');
|
|
17
|
-
if (!link) return;
|
|
18
|
-
if (link.target === '_blank') return;
|
|
19
|
-
//event delegation
|
|
20
|
-
|
|
21
|
-
e.preventDefault();
|
|
22
|
-
appendElement(body, gardener({
|
|
23
|
-
t: 'div',
|
|
24
|
-
cn: ['tempnpdiv', 'top-0', 'left-[100vw]', 'absolute', 'h-screen', 'w-screen', 'shadow-[30px_0_60px_15px_rgb(0,0,0)]'],
|
|
25
|
-
}))
|
|
26
|
-
const width = window.innerWidth
|
|
27
|
-
console.log(width)
|
|
28
|
-
body.style.transition = '.2s';
|
|
29
|
-
body.style.transform = `translateX(-${width}px)`
|
|
30
|
-
setTimeout(() => {
|
|
31
|
-
window.location.href = link.href
|
|
32
|
-
}, 200)
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
window.addEventListener('pagehide', () => {
|
|
37
|
-
setTimeout(() => {
|
|
38
|
-
body.style.transform = 'translateX(0px)';
|
|
39
|
-
setTimeout(() => {
|
|
40
|
-
fetchElement('.tempnpdiv').remove()
|
|
41
|
-
|
|
42
|
-
}, 200)
|
|
43
|
-
}, 200);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function pageloader() {
|
|
49
|
-
const loader = fetchElement('.loader');
|
|
50
|
-
loader.style.transition = '.4s';
|
|
51
|
-
loader.style.opacity = '0';
|
|
52
|
-
setTimeout(() => loader.remove(), 400)
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
//console.log('hellooo');
|
|
56
|
-
//parser(fetchElement('.hero'));
|
|
57
|
-
//parser(fetchElement('nav'));
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Gardener – Declarative DOM Library</title>
|
|
7
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
-
<!-- Assume your custom styles are still here -->
|
|
9
|
-
<link href="/style.css" rel="stylesheet"/>
|
|
10
|
-
</head>
|
|
11
|
-
|
|
12
|
-
<body class="bg-slate-50 text-slate-900 antialiased">
|
|
13
|
-
|
|
14
|
-
<div class="max-w-7xl mx-auto px-6 py-12 space-y-20">
|
|
15
|
-
|
|
16
|
-
<!-- Hero / Introduction -->
|
|
17
|
-
<section class="bg-gradient-to-br from-emerald-50 to-cyan-50 rounded-3xl p-10 lg:p-16 shadow-xl">
|
|
18
|
-
<div class="flex flex-col lg:flex-row items-center justify-between gap-12">
|
|
19
|
-
<div class="max-w-2xl space-y-6">
|
|
20
|
-
<h1 class="text-4xl lg:text-5xl font-bold text-slate-800">Gardener</h1>
|
|
21
|
-
<p class="text-xl text-slate-700 leading-relaxed">
|
|
22
|
-
Gardener is a lightweight front-end library for creating and manipulating DOM elements using a clean, declarative JavaScript object syntax.
|
|
23
|
-
</p>
|
|
24
|
-
<p class="text-lg text-slate-600 leading-relaxed">
|
|
25
|
-
It comes with a development server featuring hot reload, on-the-fly component creation from existing HTML, dynamic image resizing & caching, and zero virtual DOM / JSX / build-step philosophy.
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<img
|
|
30
|
-
src="/cache/gardener_500x500.webp"
|
|
31
|
-
alt="Gardener logo"
|
|
32
|
-
class="w-64 lg:w-80 rounded-2xl shadow-2xl"
|
|
33
|
-
/>
|
|
34
|
-
</div>
|
|
35
|
-
</section>
|
|
36
|
-
|
|
37
|
-
<!-- Design Philosophy -->
|
|
38
|
-
<section class="bg-linear-to-br from-slate-900 to-slate-800 rounded-3xl p-10 lg:p-12 shadow-2xl space-y-6">
|
|
39
|
-
<h2 class="text-3xl font-bold">Design Philosophy</h2>
|
|
40
|
-
<ul class="list-disc list-inside space-y-3 text-lg">
|
|
41
|
-
<li>No virtual DOM</li>
|
|
42
|
-
<li>No JSX</li>
|
|
43
|
-
<li>No compilation / build step</li>
|
|
44
|
-
<li>DOM-first and fully deterministic</li>
|
|
45
|
-
<li>Everything is explicit and inspectable in the browser</li>
|
|
46
|
-
</ul>
|
|
47
|
-
</section>
|
|
48
|
-
|
|
49
|
-
<!-- Core API -->
|
|
50
|
-
<section class="space-y-10">
|
|
51
|
-
<h2 class="text-3xl font-bold text-center lg:text-left">Core Utilities</h2>
|
|
52
|
-
|
|
53
|
-
<div class="grid md:grid-cols-2 gap-8">
|
|
54
|
-
<div class="bg-white p-8 rounded-2xl shadow space-y-4">
|
|
55
|
-
<h3 class="text-2xl font-semibold">DOM Helpers</h3>
|
|
56
|
-
<ul class="list-disc list-inside space-y-2 text-slate-700">
|
|
57
|
-
<li><code class="font-mono">fetchElement(selector)</code> — safe <code>querySelector</code> wrapper</li>
|
|
58
|
-
<li><code>appendElement(parent, child)</code> — append child node</li>
|
|
59
|
-
<li><code>replaceElement(oldElement, newElement)</code> — replace DOM node</li>
|
|
60
|
-
<li><code>createElement(type, classes)</code> — low-level element factory</li>
|
|
61
|
-
<li><code>imagePreloader(images)</code> — preload images efficiently</li>
|
|
62
|
-
</ul>
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<div class="bg-white p-8 rounded-2xl shadow space-y-4">
|
|
66
|
-
<h3 class="text-2xl font-semibold">Gardener – The Declarative Builder</h3>
|
|
67
|
-
<pre class="bg-slate-900 text-slate-100 text-sm p-5 rounded-xl overflow-x-auto font-mono">
|
|
68
|
-
<code>gardener({
|
|
69
|
-
t: 'div',
|
|
70
|
-
cn: ['p-6', 'flex', 'gap-4'],
|
|
71
|
-
attr: { id: 'hero', role: 'banner' },
|
|
72
|
-
txt: 'Welcome',
|
|
73
|
-
events: {
|
|
74
|
-
click: () => console.log('clicked!')
|
|
75
|
-
},
|
|
76
|
-
children: [ /* nested objects */ ]
|
|
77
|
-
})</code>
|
|
78
|
-
</pre>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<div class="bg-white p-8 rounded-2xl shadow overflow-x-auto">
|
|
83
|
-
<table class="w-full text-left border border-slate-200">
|
|
84
|
-
<thead class="bg-slate-100">
|
|
85
|
-
<tr>
|
|
86
|
-
<th class="p-4 font-semibold">Key</th>
|
|
87
|
-
<th class="p-4 font-semibold">Description</th>
|
|
88
|
-
</tr>
|
|
89
|
-
</thead>
|
|
90
|
-
<tbody class="text-slate-700">
|
|
91
|
-
<tr><td class="p-4 border-t"><code>t</code></td> <td class="p-4 border-t">HTML/SVG tag name</td></tr>
|
|
92
|
-
<tr><td class="p-4 border-t"><code>cn</code></td> <td class="p-4 border-t">Array of class names</td></tr>
|
|
93
|
-
<tr><td class="p-4 border-t"><code>attr</code></td> <td class="p-4 border-t">Attributes / properties object</td></tr>
|
|
94
|
-
<tr><td class="p-4 border-t"><code>txt</code></td> <td class="p-4 border-t">Text content (string)</td></tr>
|
|
95
|
-
<tr><td class="p-4 border-t"><code>events</code></td><td class="p-4 border-t">Event listeners <code>{ event: handler }</code></td></tr>
|
|
96
|
-
<tr><td class="p-4 border-t"><code>children</code></td><td class="p-4 border-t">Array of nested gardener objects</td></tr>
|
|
97
|
-
</tbody>
|
|
98
|
-
</table>
|
|
99
|
-
</div>
|
|
100
|
-
</section>
|
|
101
|
-
|
|
102
|
-
<!-- Runtime Configuration -->
|
|
103
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
104
|
-
<h2 class="text-3xl font-bold">Runtime Configuration</h2>
|
|
105
|
-
<pre class="bg-slate-900 text-slate-100 p-6 rounded-xl overflow-x-auto font-mono text-sm">
|
|
106
|
-
<code>const config = {
|
|
107
|
-
mode: 'dev', // 'dev' | 'prod'
|
|
108
|
-
componentdir: 'components',
|
|
109
|
-
hotreload: true
|
|
110
|
-
}</code>
|
|
111
|
-
</pre>
|
|
112
|
-
<ul class="list-disc list-inside space-y-3 text-slate-700">
|
|
113
|
-
<li><strong>mode</strong>: <code>dev</code> → enables parser, hot reload, dev UI; <code>prod</code> → disables everything</li>
|
|
114
|
-
<li><strong>componentdir</strong>: folder for auto-generated component files</li>
|
|
115
|
-
<li><strong>hotreload</strong>: persisted in localStorage</li>
|
|
116
|
-
</ul>
|
|
117
|
-
</section>
|
|
118
|
-
|
|
119
|
-
<!-- Hot Reload -->
|
|
120
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
121
|
-
<h2 class="text-3xl font-bold">Hot Reload (Dev Mode)</h2>
|
|
122
|
-
<ul class="list-disc list-inside space-y-3 text-slate-700">
|
|
123
|
-
<li>State saved in <code>localStorage</code></li>
|
|
124
|
-
<li>Auto-reload after ~1 second when enabled</li>
|
|
125
|
-
<li>Disabling cancels pending timers</li>
|
|
126
|
-
</ul>
|
|
127
|
-
<div class="inline-block bg-slate-100 px-5 py-3 rounded-xl mt-4">
|
|
128
|
-
<p class="font-semibold">Toggle shortcut: <kbd class="bg-white px-2 py-1 rounded border border-slate-300">Ctrl + H</kbd></p>
|
|
129
|
-
</div>
|
|
130
|
-
</section>
|
|
131
|
-
|
|
132
|
-
<!-- SVG Support -->
|
|
133
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
134
|
-
<h2 class="text-3xl font-bold">SVG Support</h2>
|
|
135
|
-
<p class="text-slate-700">
|
|
136
|
-
Gardener automatically uses the SVG namespace for these tags:
|
|
137
|
-
</p>
|
|
138
|
-
<pre class="bg-slate-900 text-emerald-300 font-mono p-5 rounded-xl inline-block">
|
|
139
|
-
<code>svg, path, circle, rect, line, polygon, polyline, g, defs, clipPath, use</code>
|
|
140
|
-
</pre>
|
|
141
|
-
<p class="text-slate-700">All attributes are applied via <code>setAttribute</code> safely.</p>
|
|
142
|
-
</section>
|
|
143
|
-
|
|
144
|
-
<!-- Creating & Using Components -->
|
|
145
|
-
<section class="grid lg:grid-cols-2 gap-10">
|
|
146
|
-
<div class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
147
|
-
<h2 class="text-3xl font-bold">Creating Components</h2>
|
|
148
|
-
<ol class="list-decimal list-inside space-y-4 text-slate-700">
|
|
149
|
-
<li>Write normal HTML + classes (give parent an <code>id</code> or unique selector)</li>
|
|
150
|
-
<li>Run: <code>parser(fetchElement('#your-id'))</code></li>
|
|
151
|
-
<li>Popup appears → choose component name</li>
|
|
152
|
-
<li>File saved to <code>/src/frontend/components/YourName.js</code></li>
|
|
153
|
-
</ol>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
<div class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
157
|
-
<h2 class="text-3xl font-bold">Using Components</h2>
|
|
158
|
-
<pre class="bg-slate-900 text-slate-100 p-6 rounded-xl font-mono text-sm overflow-x-auto">
|
|
159
|
-
<code>import { myHeader } from '/components/myHeader.js'
|
|
160
|
-
|
|
161
|
-
replaceElement(
|
|
162
|
-
fetchElement('#header-placeholder'),
|
|
163
|
-
myHeader()
|
|
164
|
-
)</code>
|
|
165
|
-
</pre>
|
|
166
|
-
</div>
|
|
167
|
-
</section>
|
|
168
|
-
|
|
169
|
-
<!-- Image Optimization -->
|
|
170
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
171
|
-
<h2 class="text-3xl font-bold">Image Optimization & Caching</h2>
|
|
172
|
-
<ul class="list-disc list-inside space-y-3 text-slate-700">
|
|
173
|
-
<li>Place originals in <code>/src/frontend/assets/</code></li>
|
|
174
|
-
<li>Use: <code><img src="/cache/photo_800x600.webp"></code></li>
|
|
175
|
-
</ul>
|
|
176
|
-
<p class="text-slate-600 mt-4">
|
|
177
|
-
Server auto-converts to WebP, resizes, and caches on demand.
|
|
178
|
-
</p>
|
|
179
|
-
</section>
|
|
180
|
-
|
|
181
|
-
<!-- Development Tools -->
|
|
182
|
-
<section class="space-y-10">
|
|
183
|
-
<h2 class="text-3xl font-bold text-center">Development Tools</h2>
|
|
184
|
-
|
|
185
|
-
<div class="grid md:grid-cols-2 gap-8">
|
|
186
|
-
<div class="bg-white p-8 rounded-2xl shadow space-y-4">
|
|
187
|
-
<h3 class="text-2xl font-semibold">Create New Page</h3>
|
|
188
|
-
<ol class="list-decimal list-inside space-y-2 text-slate-700">
|
|
189
|
-
<li>Click floating <strong>+</strong> button (dev mode only)</li>
|
|
190
|
-
<li>Enter route (<code>/about</code>, etc.)</li>
|
|
191
|
-
<li>New page created & browser navigates</li>
|
|
192
|
-
</ol>
|
|
193
|
-
<p class="text-sm text-slate-500 mt-4">
|
|
194
|
-
Template: <code>/src/backend/frontendtemplate.ejs</code>
|
|
195
|
-
</p>
|
|
196
|
-
</div>
|
|
197
|
-
|
|
198
|
-
<div class="bg-white p-8 rounded-2xl shadow space-y-4">
|
|
199
|
-
<h3 class="text-2xl font-semibold">Generate Static Site</h3>
|
|
200
|
-
<p class="text-slate-700">
|
|
201
|
-
Visit <code>/createstatic</code> to build a fully static version in
|
|
202
|
-
<code>/src/frontendStatic</code>
|
|
203
|
-
</p>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
</section>
|
|
207
|
-
|
|
208
|
-
</div>
|
|
209
|
-
|
|
210
|
-
<script src="/global.js" type="module"></script>
|
|
211
|
-
<script type="module">
|
|
212
|
-
import { parser, fetchElement } from "/gardener.js";
|
|
213
|
-
// parser(fetchElement("#some-selector")); // uncomment when needed
|
|
214
|
-
</script>
|
|
215
|
-
</body>
|
|
216
|
-
</html>
|