create-gardener 1.1.13 → 2.0.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/package.json +1 -1
- package/starter.js +5 -2
- package/template/.env +0 -0
- package/template/buildHelper.js +3 -0
- package/template/jsconfig.json +27 -0
- package/template/package.json +6 -5
- package/template/src/backend/controllers/gardener/addComponent.ts +27 -0
- package/template/src/backend/controllers/gardener/addPage.ts +60 -0
- package/template/src/backend/controllers/gardener/createStatic.ts +84 -0
- package/template/src/backend/controllers/gardener/imageOptimiser.ts +67 -0
- package/template/src/backend/controllers/gardener/index.ts +4 -0
- package/template/src/backend/libs/generateWebp.ts +1 -0
- package/template/src/backend/routes/gardener.route.ts +15 -5
- package/template/src/backend/server.ts +8 -10
- package/template/src/frontend/assets/favicon.png +0 -0
- package/template/src/frontend/frontendtemplate.ejs +5 -17
- package/template/src/frontend/static/cache/favicon_500x500.webp +0 -0
- package/template/src/frontend/static/cache/favicon_50x50.webp +0 -0
- package/template/src/frontend/static/cache/gardener_50x50.webp +0 -0
- package/template/src/frontend/static/components/copybtn.js +86 -0
- package/template/src/frontend/static/components/nonui/api.js +33 -16
- package/template/src/frontend/static/components/nonui/navigation.js +59 -0
- package/template/src/frontend/static/components/secondtest.js +6 -0
- package/template/src/frontend/static/components/{emailsvg.js → testdd.js} +7 -7
- package/template/src/frontend/static/gardener.js +0 -388
- package/template/src/frontend/static/gardenerConfig.js +1 -0
- package/template/src/frontend/static/gardenerDev.js +408 -0
- package/template/src/frontend/static/global.js +2 -56
- package/template/src/frontend/static/pages/_.js +13 -0
- package/template/src/frontend/static/pages/_get-started.js +5 -0
- package/template/src/frontend/static/style.css +1039 -1
- package/template/src/frontend/static/style2.css +1 -1
- package/template/src/frontend/static/zod.js +8 -0
- package/template/src/frontend/views/_.ejs +96 -216
- package/template/src/frontend/views/_get-started.ejs +25 -0
- package/template/src/frontend/views/partials/icons/clipboard.ejs +1 -0
- package/template/src/frontend/views/partials/icons/clipboardok.ejs +1 -0
- package/template/tsconfig.json +1 -1
- package/template/src/backend/controllers/gardener.controller.ts +0 -193
- package/template/src/frontend/gardenerST.js +0 -431
- package/template/src/frontend/static/components/eyeoff.js +0 -50
- package/template/src/frontend/static/components/eyeon.js +0 -43
- package/template/src/frontend/static/components/passwordBox.js +0 -105
- package/template/src/frontend/views/_login.ejs +0 -75
- package/template/src/frontend/views/partials/loader.ejs +0 -3
|
@@ -1,234 +1,114 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
</head>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<div
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
</p>
|
|
29
|
-
<p class="text-lg text-slate-600 leading-relaxed">
|
|
30
|
-
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.
|
|
31
|
-
</p>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/static/cache/favicon_50x50.webp" />
|
|
7
|
+
<link href="/static/style.css" rel="stylesheet"/>
|
|
8
|
+
<link href="/static/style2.css" rel="stylesheet"/>
|
|
9
|
+
<title>Gardener: The Mini Web Framework</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body class="bg-slate-50 text-slate-900 font-sans">
|
|
12
|
+
|
|
13
|
+
<div class='loader w-screen h-screen bg-white fixed z-2'> </div>
|
|
14
|
+
<div class='notification'></div>
|
|
15
|
+
|
|
16
|
+
<div id='main'>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
<!-- hero -->
|
|
20
|
+
<div class='hero flex-wrap w-full h-screen bg-green-200 flex justify-center items-center px-10'>
|
|
21
|
+
<div class='w-200'>
|
|
22
|
+
<a href="https://ritish.site">Home</a> > <a href="https://ritish.site/projects">Projects</a> > <a href="/">Gardener</a>
|
|
23
|
+
<h1 class='text-6xl my-10 font-bold text-green-900'>Gardener</h1>
|
|
24
|
+
<h2 class='text-2xl font-bold text-green-800 mb-4'>Develop With No Bloat</h2>
|
|
25
|
+
<p class="text-lg mb-6 max-w-md">Gardener is a lightweight, DOM-first front-end library. No virtual DOM, no JSX, and no compilation step. Just the real DOM.</p>
|
|
26
|
+
<a class="bg-green-700 text-white px-8 py-3 rounded-lg font-bold hover:bg-green-800 transition" href='/get-started'>Get Started</a>
|
|
27
|
+
<a class="border border-green-700 text-black px-8 py-3 rounded-lg font-bold hover:text-white hover:bg-green-800 transition" href='https://github.com/ritishDas/Gardener'>View On Github</a>
|
|
32
28
|
</div>
|
|
33
|
-
|
|
34
|
-
<img
|
|
35
|
-
src="/static/cache/gardener_500x500.webp"
|
|
36
|
-
alt="Gardener logo"
|
|
37
|
-
class="w-64 lg:w-80 rounded-2xl shadow-2xl"
|
|
38
|
-
/>
|
|
29
|
+
<img src="/static/cache/gardener_500x500.webp" alt="Gardener Logo" class="w-96 h-96 object-contain ml-10">
|
|
39
30
|
</div>
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<h3 class="text-2xl font-semibold">DOM Helpers</h3>
|
|
63
|
-
<ul class="list-disc list-inside space-y-2 text-slate-700">
|
|
64
|
-
<li><code class="font-mono">fetchElement(selector)</code> — safe <code>querySelector</code> wrapper</li>
|
|
65
|
-
<li><code>appendElement(parent, child)</code> — append child node</li>
|
|
66
|
-
<li><code>replaceElement(oldElement, newElement)</code> — replace DOM node</li>
|
|
67
|
-
<li><code>createElement(type, classes)</code> — low-level element factory</li>
|
|
68
|
-
<li><code>imagePreloader(images)</code> — preload images efficiently</li>
|
|
32
|
+
<!-- Copy command -->
|
|
33
|
+
<div class="flex w-1/4 items-center mx-auto my-15 justify-between gap-4 bg-[#1e1e1e] border border-gray-700 rounded-lg px-4 py-3 font-mono shadow-sm group">
|
|
34
|
+
<span class="text-sm md:text-base text-gray-300 select-all">
|
|
35
|
+
<span class="text-emerald-400 mr-2">$</span><span class='initCommand'>pnpm create gardener app</span>
|
|
36
|
+
</span>
|
|
37
|
+
|
|
38
|
+
<button class="copybtn flex items-center justify-center p-2 text-gray-400 hover:text-white hover:bg-white/10 rounded transition-all duration-200 active:scale-95" title="Copy to clipboard">
|
|
39
|
+
<span class="w-5 h-5" >
|
|
40
|
+
<%- include('partials/icons/clipboard') %>
|
|
41
|
+
</span>
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
<!-- Philosophy -->
|
|
45
|
+
<div class="max-w-5xl mx-auto py-20 px-6 grid md:grid-cols-2 gap-12">
|
|
46
|
+
<div>
|
|
47
|
+
<h3 class="text-3xl font-bold mb-4 text-green-900">✨ Philosophy</h3>
|
|
48
|
+
<ul class="space-y-3 text-slate-700">
|
|
49
|
+
<li><strong>DOM-First:</strong> Renders directly to the real DOM.</li>
|
|
50
|
+
<li><strong>Deterministic:</strong> If you can inspect it, you can understand it.</li>
|
|
51
|
+
<li><strong>Native:</strong> Works in the browser via ES modules.</li>
|
|
52
|
+
<li><strong>Zero Build:</strong> No bundlers or magic required.</li>
|
|
69
53
|
</ul>
|
|
70
54
|
</div>
|
|
71
55
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
click: () => console.log('clicked!')
|
|
82
|
-
},
|
|
83
|
-
children: [ /* nested objects */ ]
|
|
84
|
-
})</code>
|
|
85
|
-
</pre>
|
|
56
|
+
<!-- Features -->
|
|
57
|
+
<div>
|
|
58
|
+
<h3 class="text-3xl font-bold mb-4 text-green-900">🚀 Features</h3>
|
|
59
|
+
<div class="grid grid-cols-2 gap-4">
|
|
60
|
+
<div class="p-4 bg-white shadow-sm border border-green-100 rounded">Declarative UI</div>
|
|
61
|
+
<div class="p-4 bg-white shadow-sm border border-green-100 rounded">SVG Namespace Support</div>
|
|
62
|
+
<div class="p-4 bg-white shadow-sm border border-green-100 rounded">Hot Reload</div>
|
|
63
|
+
<div class="p-4 bg-white shadow-sm border border-green-100 rounded">Image Optimization</div>
|
|
64
|
+
</div>
|
|
86
65
|
</div>
|
|
87
66
|
</div>
|
|
88
67
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<!-- Runtime Configuration -->
|
|
110
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
111
|
-
<h2 class="text-3xl font-bold">Runtime Configuration</h2>
|
|
112
|
-
<pre class="bg-slate-900 text-slate-100 p-6 rounded-xl overflow-x-auto font-mono text-sm">
|
|
113
|
-
<code>const config = {
|
|
114
|
-
mode: 'dev', // 'dev' | 'prod'
|
|
115
|
-
componentdir: 'components',
|
|
116
|
-
hotreload: true
|
|
117
|
-
}</code>
|
|
118
|
-
</pre>
|
|
119
|
-
<ul class="list-disc list-inside space-y-3 text-slate-700">
|
|
120
|
-
<li><strong>mode</strong>: <code>dev</code> → enables parser, hot reload, dev UI; <code>prod</code> → disables everything</li>
|
|
121
|
-
<li><strong>componentdir</strong>: folder for auto-generated component files</li>
|
|
122
|
-
<li><strong>hotreload</strong>: persisted in localStorage</li>
|
|
123
|
-
</ul>
|
|
124
|
-
</section>
|
|
125
|
-
|
|
126
|
-
<!-- Hot Reload -->
|
|
127
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
128
|
-
<h2 class="text-3xl font-bold">Hot Reload (Dev Mode)</h2>
|
|
129
|
-
<ul class="list-disc list-inside space-y-3 text-slate-700">
|
|
130
|
-
<li>State saved in <code>localStorage</code></li>
|
|
131
|
-
<li>Auto-reload after ~1 second when enabled</li>
|
|
132
|
-
<li>Disabling cancels pending timers</li>
|
|
133
|
-
</ul>
|
|
134
|
-
<div class="inline-block bg-slate-100 px-5 py-3 rounded-xl mt-4">
|
|
135
|
-
<p class="font-semibold">Toggle shortcut: <kbd class="bg-white px-2 py-1 rounded border border-slate-300">Ctrl + H</kbd></p>
|
|
136
|
-
</div>
|
|
137
|
-
</section>
|
|
138
|
-
|
|
139
|
-
<!-- SVG Support -->
|
|
140
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
141
|
-
<h2 class="text-3xl font-bold">SVG Support</h2>
|
|
142
|
-
<p class="text-slate-700">
|
|
143
|
-
Gardener automatically uses the SVG namespace for these tags:
|
|
144
|
-
</p>
|
|
145
|
-
<pre class="bg-slate-900 text-emerald-300 font-mono p-5 rounded-xl inline-block">
|
|
146
|
-
<code>svg, path, circle, rect, line, polygon, polyline, g, defs, clipPath, use</code>
|
|
147
|
-
</pre>
|
|
148
|
-
<p class="text-slate-700">All attributes are applied via <code>setAttribute</code> safely.</p>
|
|
149
|
-
</section>
|
|
150
|
-
|
|
151
|
-
<!-- Creating & Using Components -->
|
|
152
|
-
<section class="grid lg:grid-cols-2 gap-10">
|
|
153
|
-
<div class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
154
|
-
<h2 class="text-3xl font-bold">Creating Components</h2>
|
|
155
|
-
<ol class="list-decimal list-inside space-y-4 text-slate-700">
|
|
156
|
-
<li>Write normal HTML + classes (give parent an <code>id</code> or unique selector)</li>
|
|
157
|
-
<li>Run: <code>parser(fetchElement('#your-id'))</code></li>
|
|
158
|
-
<li>Popup appears → choose component name</li>
|
|
159
|
-
<li>File saved to <code>/src/frontend/components/YourName.js</code></li>
|
|
160
|
-
</ol>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<div class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
164
|
-
<h2 class="text-3xl font-bold">Using Components</h2>
|
|
165
|
-
<pre class="bg-slate-900 text-slate-100 p-6 rounded-xl font-mono text-sm overflow-x-auto">
|
|
166
|
-
<code>import { myHeader } from '/components/myHeader.js'
|
|
167
|
-
|
|
168
|
-
replaceElement(
|
|
169
|
-
fetchElement('#header-placeholder'),
|
|
170
|
-
myHeader()
|
|
171
|
-
)</code>
|
|
172
|
-
</pre>
|
|
173
|
-
</div>
|
|
174
|
-
</section>
|
|
175
|
-
|
|
176
|
-
<!-- Image Optimization -->
|
|
177
|
-
<section class="bg-white p-10 rounded-2xl shadow space-y-6">
|
|
178
|
-
<h2 class="text-3xl font-bold">Image Optimization & Caching</h2>
|
|
179
|
-
<ul class="list-disc list-inside space-y-3 text-slate-700">
|
|
180
|
-
<li>Place originals in <code>/src/frontend/assets/</code></li>
|
|
181
|
-
<li>Use: <code><img src="/static/cache/photo_800x600.webp"></code></li>
|
|
182
|
-
</ul>
|
|
183
|
-
<p class="text-slate-600 mt-4">
|
|
184
|
-
Server auto-converts to WebP, resizes, and caches on demand.
|
|
185
|
-
</p>
|
|
186
|
-
</section>
|
|
187
|
-
|
|
188
|
-
<!-- Development Tools -->
|
|
189
|
-
<section class="space-y-10">
|
|
190
|
-
<h2 class="text-3xl font-bold text-center">Development Tools</h2>
|
|
191
|
-
|
|
192
|
-
<div class="grid md:grid-cols-2 gap-8">
|
|
193
|
-
<div class="bg-white p-8 rounded-2xl shadow space-y-4">
|
|
194
|
-
<h3 class="text-2xl font-semibold">Create New Page</h3>
|
|
195
|
-
<ol class="list-decimal list-inside space-y-2 text-slate-700">
|
|
196
|
-
<li>Click floating <strong>+</strong> button (dev mode only)</li>
|
|
197
|
-
<li>Enter route (<code>/about</code>, etc.)</li>
|
|
198
|
-
<li>New page created & browser navigates</li>
|
|
199
|
-
</ol>
|
|
200
|
-
<p class="text-sm text-slate-500 mt-4">
|
|
201
|
-
Template: <code>/src/backend/frontendtemplate.ejs</code>
|
|
202
|
-
</p>
|
|
68
|
+
<!-- Api Example -->
|
|
69
|
+
<div class="bg-slate-900 py-20 text-white">
|
|
70
|
+
<div class="max-w-5xl mx-auto px-6">
|
|
71
|
+
<h3 class="text-3xl font-bold mb-8 text-center text-green-400">The Core API</h3>
|
|
72
|
+
<div class="bg-slate-800 p-6 rounded-xl border border-slate-700 font-mono text-sm overflow-x-auto">
|
|
73
|
+
<pre class="text-blue-300">
|
|
74
|
+
gardener({
|
|
75
|
+
t: <span class="text-green-300">'div'</span>,
|
|
76
|
+
cn: [<span class="text-green-300">'p-6'</span>, <span class="text-green-300">'flex'</span>],
|
|
77
|
+
attr: { id: <span class="text-green-300">'hero'</span> },
|
|
78
|
+
txt: <span class="text-green-300">'Welcome to the Garden'</span>,
|
|
79
|
+
events: {
|
|
80
|
+
click: () => console.log(<span class="text-green-300">'Growth!'</span>)
|
|
81
|
+
},
|
|
82
|
+
children: [
|
|
83
|
+
{ t: <span class="text-green-300">'span'</span>, txt: <span class="text-green-300">'Pure DOM nodes.'</span> }
|
|
84
|
+
]
|
|
85
|
+
})
|
|
86
|
+
</pre>
|
|
87
|
+
</div>
|
|
203
88
|
</div>
|
|
89
|
+
</div>
|
|
204
90
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
91
|
+
<div class="max-w-5xl mx-auto py-20 px-6 text-center">
|
|
92
|
+
<h3 class="text-3xl font-bold mb-4 text-green-900">🧠 Simple State</h3>
|
|
93
|
+
<p class="mb-8 text-slate-600">No proxies. No diffing. Just clean, reactive callbacks.</p>
|
|
94
|
+
<div class="inline-block text-left bg-white p-6 rounded shadow-lg border-t-4 border-green-500 font-mono text-sm">
|
|
95
|
+
<p class="text-slate-500">// Initialize</p>
|
|
96
|
+
<p>const count = new State(0);</p>
|
|
97
|
+
<p class="text-slate-500 mt-2">// React</p>
|
|
98
|
+
<p>count.registerCb(val => updateUI(val));</p>
|
|
211
99
|
</div>
|
|
212
100
|
</div>
|
|
213
|
-
</section>
|
|
214
|
-
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
<!-- <div class='test'> -->
|
|
218
|
-
<!-- </div> -->
|
|
219
|
-
|
|
220
|
-
<script src="/static/global.js" type="module"></script>
|
|
221
|
-
<script type="module">
|
|
222
|
-
import { gardener,parser, fetchElement,replaceElement } from "/static/gardener.js";
|
|
223
|
-
// import parameterised from '/static/components/test.js';
|
|
224
|
-
|
|
225
|
-
// parser('.test')
|
|
226
|
-
// replaceElement('.test', parameterised())
|
|
227
101
|
|
|
102
|
+
<!-- Footer -->
|
|
103
|
+
<footer class="bg-green-900 text-green-100 py-12 text-center">
|
|
104
|
+
<p class="text-xl italic">"Because sometimes you don't need a forest. Just a garden."</p>
|
|
105
|
+
<div class="mt-6 text-sm opacity-70">
|
|
106
|
+
MIT Licensed | Built on Express & EJS
|
|
107
|
+
</div>
|
|
108
|
+
</footer>
|
|
109
|
+
</div>
|
|
228
110
|
|
|
229
|
-
</
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
</body>
|
|
111
|
+
</body>
|
|
112
|
+
<script type='module' src='/static/global.js'> </script>
|
|
113
|
+
<script type='module' src='/static/pages/_.js'> </script>
|
|
233
114
|
</html>
|
|
234
|
-
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
<link rel="icon" type="image/svg+xml" href="/static/cache/favicon_50x50.webp" />
|
|
7
|
+
<link href="/static/style.css" rel="stylesheet"/>
|
|
8
|
+
<link href="/static/style2.css" rel="stylesheet"/>
|
|
9
|
+
<title>Gardener: Get Started</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div class='loader w-screen h-screen bg-white fixed z-2'> </div>
|
|
13
|
+
<div class='notification'></div>
|
|
14
|
+
<div id='main'>
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
</body>
|
|
20
|
+
|
|
21
|
+
<script src="/static/pages/_get-started.js" type='module'></script>
|
|
22
|
+
<script type='module' src='/static/global.js'> </script>
|
|
23
|
+
</html>
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-clipboard"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 5a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-clipboard-check"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 5a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2" /><path d="M9 14l2 2l4 -4" /></svg>
|
package/template/tsconfig.json
CHANGED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import type { Request, Response } from "express";
|
|
2
|
-
import fs, { readFile, readFileSync } from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import ejs from "ejs";
|
|
5
|
-
import fsp from "fs/promises";
|
|
6
|
-
import generateWebP from "../libs/generateWebp.js";
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
8
|
-
const availableCache: Record<string, boolean> = {};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
|
|
14
|
-
interface AddComponentBody {
|
|
15
|
-
path: string;
|
|
16
|
-
component: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function addComponent(req: Request<{}, {}, AddComponentBody>, res: Response) {
|
|
20
|
-
try {
|
|
21
|
-
const { path: filePath, component } = req.body;
|
|
22
|
-
|
|
23
|
-
await fsp.mkdir('./src/frontend/static/components', { recursive: true });
|
|
24
|
-
|
|
25
|
-
fs.writeFileSync(`./src/frontend/${filePath}`, component, "utf8");
|
|
26
|
-
|
|
27
|
-
res.json({ success: true });
|
|
28
|
-
} catch (err) {
|
|
29
|
-
const error = err as Error;
|
|
30
|
-
res.json({ success: false, msg: error.message });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export async function imageOptimiser(req: Request, res: Response) {
|
|
36
|
-
try {
|
|
37
|
-
const { name } = req.params;
|
|
38
|
-
|
|
39
|
-
if (typeof name !== 'string') return;
|
|
40
|
-
// name format: test_500x300.webp
|
|
41
|
-
const match = name.match(/^(.+?)_(\d+)x(\d+)\.webp$/);
|
|
42
|
-
|
|
43
|
-
if (!match) {
|
|
44
|
-
return res.status(400).json({ error: "Invalid image format" });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const [, baseName, widthStr, heightStr] = match;
|
|
48
|
-
|
|
49
|
-
if (!widthStr || !heightStr) return;
|
|
50
|
-
const width = parseInt(widthStr, 10);
|
|
51
|
-
const height = parseInt(heightStr, 10);
|
|
52
|
-
|
|
53
|
-
const cacheDir = path.join(__dirname, "../../frontend/static/cache");
|
|
54
|
-
await fsp.mkdir(cacheDir, { recursive: true });
|
|
55
|
-
|
|
56
|
-
const outputPath = path.join(cacheDir, name);
|
|
57
|
-
|
|
58
|
-
// 1️⃣ Return cached file if exists
|
|
59
|
-
try {
|
|
60
|
-
await fsp.access(outputPath);
|
|
61
|
-
return res.sendFile(path.basename(outputPath), {
|
|
62
|
-
root: path.dirname(outputPath),
|
|
63
|
-
});
|
|
64
|
-
} catch {
|
|
65
|
-
// not cached → continue
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 2️⃣ Find source image with same base name
|
|
69
|
-
const assetsDir = path.resolve("./src/frontend/assets");
|
|
70
|
-
const files = await fsp.readdir(assetsDir);
|
|
71
|
-
|
|
72
|
-
const sourceFile = files.find((file) => {
|
|
73
|
-
const parsed = path.parse(file);
|
|
74
|
-
return parsed.name === baseName;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
if (!sourceFile) {
|
|
78
|
-
return res.status(404).json({ error: "Source image not found" });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const inputPath = path.join(assetsDir, sourceFile);
|
|
82
|
-
|
|
83
|
-
// 3️⃣ Generate optimized WebP
|
|
84
|
-
await generateWebP(inputPath, outputPath, width, height);
|
|
85
|
-
|
|
86
|
-
// 4️⃣ Return generated file
|
|
87
|
-
return res.sendFile(path.basename(outputPath), {
|
|
88
|
-
root: path.dirname(outputPath),
|
|
89
|
-
});
|
|
90
|
-
} catch (err) {
|
|
91
|
-
console.error(err);
|
|
92
|
-
return res.status(500).json({ error: "Image optimisation failed" });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export async function addPage(req: Request, res: Response) {
|
|
97
|
-
try {
|
|
98
|
-
const pagename: string = req.body.page;
|
|
99
|
-
const buffer = readFileSync('./src/frontend/frontendtemplate.ejs', 'utf8');
|
|
100
|
-
const name = pagename.replaceAll('/', '_');
|
|
101
|
-
|
|
102
|
-
fs.writeFileSync(`./src/frontend/views/${name}.ejs`, buffer, "utf8");
|
|
103
|
-
|
|
104
|
-
fs.appendFileSync('./src/backend/routes/gardener.route.ts', ` router.route("${pagename}").get((req: Request, res: Response) => res.render("${name}"))\n `);
|
|
105
|
-
|
|
106
|
-
await fsp.mkdir('src/frontend/static/pages', { recursive: true });
|
|
107
|
-
|
|
108
|
-
fs.writeFileSync(`./src/frontend/static/pages/${name}.js`, 'import { log, parser, fetchElement, replaceElement, appendElement, State, addEL } from "/static/gardener.js";', "utf8");
|
|
109
|
-
res.json({ success: true });
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
const error = err as Error;
|
|
113
|
-
res.json({ success: false, msg: error.message });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
export async function createStatic(req: Request, res: Response) {
|
|
120
|
-
try {
|
|
121
|
-
const viewsDir = path.resolve("src/frontend/views");
|
|
122
|
-
const outDir = path.resolve("src/tempfrontend");
|
|
123
|
-
const finalOut = path.resolve("src/frontendStatic");
|
|
124
|
-
|
|
125
|
-
await fsp.mkdir(outDir, { recursive: true });
|
|
126
|
-
await fsp.mkdir(finalOut, { recursive: true });
|
|
127
|
-
|
|
128
|
-
const entries = await fsp.readdir(viewsDir, { withFileTypes: true });
|
|
129
|
-
|
|
130
|
-
const rendered: string[] = [];
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
for (const entry of entries) {
|
|
134
|
-
// skip folders (partials, layouts, etc.)
|
|
135
|
-
if (!entry.isFile()) continue;
|
|
136
|
-
if (!entry.name.endsWith(".ejs")) continue;
|
|
137
|
-
|
|
138
|
-
const inputPath = path.join(viewsDir, entry.name);
|
|
139
|
-
const outputName = entry.name.replace(/\.ejs$/, ".html");
|
|
140
|
-
const outputPath = path.join(outDir, outputName);
|
|
141
|
-
|
|
142
|
-
const html = await ejs.renderFile(
|
|
143
|
-
inputPath,
|
|
144
|
-
{
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
// async: true,
|
|
148
|
-
views: [viewsDir], // needed for includes
|
|
149
|
-
}
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
await fsp.writeFile(outputPath, html, "utf8");
|
|
153
|
-
rendered.push(outputName);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const entries3 = await fsp.readdir(outDir, { withFileTypes: true });
|
|
157
|
-
for (const entry of entries3) {
|
|
158
|
-
|
|
159
|
-
// "_path1_path2_path3.html" -> ["path1", "path2", "path3"]
|
|
160
|
-
const parts = entry.name
|
|
161
|
-
.replace(/^_/, "")
|
|
162
|
-
.replace(/\.html$/, "")
|
|
163
|
-
.split("_");
|
|
164
|
-
|
|
165
|
-
const targetDir = path.join(finalOut, ...parts);
|
|
166
|
-
const targetFile = path.join(targetDir, "index.html");
|
|
167
|
-
|
|
168
|
-
// ensure directories exist
|
|
169
|
-
await fsp.mkdir(targetDir, { recursive: true });
|
|
170
|
-
console.log('done');
|
|
171
|
-
// copy file
|
|
172
|
-
await fsp.copyFile(path.join(outDir, entry.name), targetFile);
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
await fsp.rm(outDir, { recursive: true, force: true });
|
|
176
|
-
await fsp.cp(
|
|
177
|
-
path.resolve("src/frontend/static"),
|
|
178
|
-
path.join(finalOut, 'static'),
|
|
179
|
-
{ recursive: true }
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
return res.json({
|
|
183
|
-
success: true,
|
|
184
|
-
generated: rendered,
|
|
185
|
-
outDir,
|
|
186
|
-
});
|
|
187
|
-
} catch (err) {
|
|
188
|
-
console.error(err);
|
|
189
|
-
return res.status(500).json({ error: "Static build failed" });
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|