create-gardener 2.0.9 → 2.1.1
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/README.md +328 -140
- package/package.json +1 -1
- package/template/src/backend/controllers/gardener/addPage.ts +25 -21
- package/template/src/backend/controllers/gardener/createStatic.ts +1 -0
- package/template/src/backend/libs/generateWebp.ts +0 -2
- package/template/src/frontend/static/cache/gardener_100x100.webp +0 -0
- package/template/src/frontend/static/components/copybtn.js +16 -3
- package/template/src/frontend/static/components/footer.js +33 -0
- package/template/src/frontend/static/components/gardener/errorBox.js +47 -0
- package/template/src/frontend/static/components/gardener/hotReloadbtn.js +82 -0
- package/template/src/frontend/static/components/gardener/pageOverlayBtn.js +138 -0
- package/template/src/frontend/static/components/gardener/parserWindow.js +159 -0
- package/template/src/frontend/static/components/nonui/api.js +15 -2
- package/template/src/frontend/static/gardener.js +129 -58
- package/template/src/frontend/static/gardenerDev.js +65 -399
- package/template/src/frontend/static/global.js +1 -1
- package/template/src/frontend/static/pages/pages._.js +5 -0
- package/template/src/frontend/static/style.css +101 -0
- package/template/src/frontend/static/style2.css +2 -2
- package/template/src/frontend/template/template._.ejs +121 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
import { gardener, fetchElement, replaceElement } from '../gardener.js'
|
|
3
|
+
|
|
4
|
+
export function footer({mystery}) {
|
|
5
|
+
return gardener({
|
|
6
|
+
"t": "footer",
|
|
7
|
+
"cn": [
|
|
8
|
+
"bg-green-900",
|
|
9
|
+
"text-green-100",
|
|
10
|
+
"py-12",
|
|
11
|
+
"text-center"
|
|
12
|
+
],
|
|
13
|
+
"children": [
|
|
14
|
+
{
|
|
15
|
+
"t": "p",
|
|
16
|
+
"cn": [
|
|
17
|
+
"text-xl",
|
|
18
|
+
"italic"
|
|
19
|
+
],
|
|
20
|
+
"txt": "\"Because sometimes you don't need a forest. Just a garden.\""
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"t": "div",
|
|
24
|
+
"cn": [
|
|
25
|
+
"mt-6",
|
|
26
|
+
"text-sm",
|
|
27
|
+
"opacity-70"
|
|
28
|
+
],
|
|
29
|
+
"txt": "MIT Licensed | Built on Express & EJS "+mystery+""
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { gardener, fetchElement, appendElement } from '../../gardener.js'
|
|
2
|
+
|
|
3
|
+
const body = fetchElement('body');
|
|
4
|
+
|
|
5
|
+
export function gardenerError(error) {
|
|
6
|
+
appendElement(body, gardener({
|
|
7
|
+
t: 'div',
|
|
8
|
+
// Added: centering, shadow, border-left for "alert" feel, and high z-index
|
|
9
|
+
cn: [
|
|
10
|
+
'fixed', 'top-1/2', 'left-1/2', '-translate-x-1/2', '-translate-y-1/2',
|
|
11
|
+
'w-11/12', 'max-w-md', 'bg-white', 'text-gray-800', 'shadow-2xl',
|
|
12
|
+
'border-l-8', 'border-red-600', 'rounded-r-lg', 'z-[100]', 'p-0', 'overflow-hidden'
|
|
13
|
+
],
|
|
14
|
+
children: [
|
|
15
|
+
{
|
|
16
|
+
t: 'div',
|
|
17
|
+
cn: ['bg-red-50', 'p-4', 'flex', 'items-center', 'gap-3'],
|
|
18
|
+
children: [
|
|
19
|
+
{
|
|
20
|
+
t: 'h2',
|
|
21
|
+
cn: ['text-red-700', 'font-bold', 'text-lg', 'uppercase', 'tracking-wider'],
|
|
22
|
+
txt: '⚠️ System Error'
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
t: 'div',
|
|
28
|
+
cn: ['p-6', 'bg-white'],
|
|
29
|
+
children: [
|
|
30
|
+
{
|
|
31
|
+
t: 'p',
|
|
32
|
+
cn: ['font-mono', 'text-sm', 'bg-gray-100', 'p-3', 'rounded', 'border', 'border-gray-200', 'break-words'],
|
|
33
|
+
txt: error
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
t: 'button',
|
|
37
|
+
cn: ['mt-4', 'w-full', 'py-2', 'bg-gray-800', 'text-white', 'rounded', 'hover:bg-black', 'transition-colors', 'cursor-pointer'],
|
|
38
|
+
txt: 'Dismiss',
|
|
39
|
+
events: {
|
|
40
|
+
click: (e) => e.target.closest('.fixed').remove()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { gardener, fetchElement, appendElement } from '../../gardener.js'
|
|
2
|
+
import { gardenerError } from './errorBox.js';
|
|
3
|
+
|
|
4
|
+
const config = {
|
|
5
|
+
hotreload: false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let hotReload;
|
|
9
|
+
let hotReloadtimeout;
|
|
10
|
+
const localStore = localStorage.getItem('hotreload');
|
|
11
|
+
|
|
12
|
+
if (localStore === null) hotReload = config.hotreload;
|
|
13
|
+
else if (localStore === 'true') hotReload = true
|
|
14
|
+
else if (localStore === 'false') hotReload = false
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export function togglehotreload() {
|
|
18
|
+
const hr = hotReload;
|
|
19
|
+
const hrcheck = fetchElement('#hrcheckbox');
|
|
20
|
+
|
|
21
|
+
localStorage.setItem('hotreload', hr);
|
|
22
|
+
|
|
23
|
+
hotReload = !hotReload;
|
|
24
|
+
|
|
25
|
+
if (hr) {
|
|
26
|
+
hrcheck.style.background = '#66e666';
|
|
27
|
+
fetchElement('.hrcheckbox').checked = true;
|
|
28
|
+
localStorage.setItem('hotreload', 'true');
|
|
29
|
+
hotReloadtimeout = setTimeout(() => window.location.reload(), 1000);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
hrcheck.style.background = 'red';
|
|
33
|
+
fetchElement('.hrcheckbox').checked = false;
|
|
34
|
+
localStorage.setItem('hotreload', 'false');
|
|
35
|
+
clearTimeout(hotReloadtimeout);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//localStorage.setItem('hotreload', hotReload);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function hotReloadBtn() {
|
|
42
|
+
return gardener({
|
|
43
|
+
t: 'p',
|
|
44
|
+
cn: ['bg-gray-200', 'fixed', 'bottom-0', 'z-100', 'right-0', 'border-b-1', 'p-2', 'rounded-md'],
|
|
45
|
+
children: [
|
|
46
|
+
{
|
|
47
|
+
t: 'span',
|
|
48
|
+
txt: 'Press '
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
t: 'span',
|
|
52
|
+
cn: ['text-green-500', 'font-bold'],
|
|
53
|
+
txt: 'Alt+h'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
t: 'span',
|
|
57
|
+
txt: ' to toggle Hot Reload'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
t: 'form',
|
|
61
|
+
attr: {
|
|
62
|
+
id: 'hrcheckbox',
|
|
63
|
+
},
|
|
64
|
+
events: {
|
|
65
|
+
click: () => togglehotreload()
|
|
66
|
+
},
|
|
67
|
+
cn: ['p-2', 'bg-red-300'],
|
|
68
|
+
children: [{
|
|
69
|
+
t: 'label',
|
|
70
|
+
txt: 'Hot Reload ',
|
|
71
|
+
}
|
|
72
|
+
, {
|
|
73
|
+
t: 'input',
|
|
74
|
+
cn: ['hrcheckbox'],
|
|
75
|
+
attr: {
|
|
76
|
+
type: 'checkbox'
|
|
77
|
+
}
|
|
78
|
+
}]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
})
|
|
82
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { gardener, fetchElement, appendElement } from '../../gardener.js'
|
|
2
|
+
|
|
3
|
+
const body = fetchElement('body');
|
|
4
|
+
|
|
5
|
+
function opnPagedialog(btn = true) {
|
|
6
|
+
if (btn) {
|
|
7
|
+
const dialog = gardener({
|
|
8
|
+
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: {
|
|
9
|
+
submit: async (e) => {
|
|
10
|
+
try {
|
|
11
|
+
e.preventDefault()
|
|
12
|
+
const data = new FormData(e.target);
|
|
13
|
+
const input = Object.fromEntries(data.entries());
|
|
14
|
+
|
|
15
|
+
const response = await fetch('/addpage', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": 'application/json'
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify(input)
|
|
21
|
+
}).then(res => res.json())
|
|
22
|
+
opnPagedialog(false)
|
|
23
|
+
window.location.href = `${input.page}`
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.log(err)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
}, children: [{
|
|
31
|
+
t: 'label',
|
|
32
|
+
txt: 'ENTER PATH FOR NEW PAGE'
|
|
33
|
+
}, { t: 'input', attr: { name: 'page' }, cn: ['pathinput'] }]
|
|
34
|
+
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
appendElement(body, dialog);
|
|
38
|
+
fetchElement('.pathinput').focus();
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
fetchElement('.addpageform').remove();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const pagebtns = gardener({
|
|
48
|
+
t: 'div',
|
|
49
|
+
cn: [
|
|
50
|
+
'fixed',
|
|
51
|
+
'bottom-20',
|
|
52
|
+
'right-4',
|
|
53
|
+
'flex',
|
|
54
|
+
'flex-col',
|
|
55
|
+
'gap-2',
|
|
56
|
+
'bg-white',
|
|
57
|
+
'shadow-lg',
|
|
58
|
+
'rounded-xl',
|
|
59
|
+
'p-3',
|
|
60
|
+
'z-50'
|
|
61
|
+
],
|
|
62
|
+
children: [
|
|
63
|
+
{
|
|
64
|
+
t: 'button',
|
|
65
|
+
cn: [
|
|
66
|
+
'px-4',
|
|
67
|
+
'py-2',
|
|
68
|
+
'bg-blue-500',
|
|
69
|
+
'text-white',
|
|
70
|
+
'rounded-lg',
|
|
71
|
+
'hover:bg-blue-600',
|
|
72
|
+
'transition'
|
|
73
|
+
],
|
|
74
|
+
children: [{ t: 'span', txt: 'New Page' }],
|
|
75
|
+
events: {
|
|
76
|
+
click: opnPagedialog
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
t: 'button',
|
|
81
|
+
cn: [
|
|
82
|
+
'px-4',
|
|
83
|
+
'py-2',
|
|
84
|
+
'bg-gray-800',
|
|
85
|
+
'text-white',
|
|
86
|
+
'rounded-lg',
|
|
87
|
+
'hover:bg-gray-900',
|
|
88
|
+
'transition'
|
|
89
|
+
],
|
|
90
|
+
children: [{ t: 'span', txt: 'Save Template' }],
|
|
91
|
+
events: {
|
|
92
|
+
click: async () => {
|
|
93
|
+
const result = await fetch('/savetemplate', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { "Content-Type": 'application/json' },
|
|
96
|
+
body: JSON.stringify({ path: fetchElement('#fileName').innerText })
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const data = await result.json(); // ✅ fix
|
|
100
|
+
alert(data.message);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
export const addPagebtn = gardener({
|
|
109
|
+
t: 'div',
|
|
110
|
+
cn: ['fixed', 'bottom-4', 'right-4', 'z-50'],
|
|
111
|
+
events: {
|
|
112
|
+
mouseenter: () => appendElement(addPagebtn, pagebtns),
|
|
113
|
+
mouseleave: () => pagebtns.remove()
|
|
114
|
+
},
|
|
115
|
+
children: [
|
|
116
|
+
{
|
|
117
|
+
t: 'span',
|
|
118
|
+
// cn: ['pb-1.5', 'flex', 'items-center', 'justify-center', 'h-15', 'w-15', 'bg-black', 'text-white', 'fixed', 'bottom-22', 'right-2'],
|
|
119
|
+
|
|
120
|
+
cn: [
|
|
121
|
+
'flex',
|
|
122
|
+
'items-center',
|
|
123
|
+
'justify-center',
|
|
124
|
+
'h-14',
|
|
125
|
+
'w-14',
|
|
126
|
+
'bg-black',
|
|
127
|
+
'text-white',
|
|
128
|
+
'fixed',
|
|
129
|
+
'bottom-22',
|
|
130
|
+
'right-2',
|
|
131
|
+
'rounded-full',
|
|
132
|
+
'shadow-lg',
|
|
133
|
+
'cursor-pointer',
|
|
134
|
+
],
|
|
135
|
+
txt: 'GR'
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { gardener, fetchElement, appendElement } from '../../gardener.js'
|
|
2
|
+
import { gardenerError } from './errorBox.js';
|
|
3
|
+
|
|
4
|
+
const body = fetchElement('body');
|
|
5
|
+
|
|
6
|
+
const config = {
|
|
7
|
+
componentdir: 'static/components',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let result;
|
|
11
|
+
export function parserWindow(text) {
|
|
12
|
+
result = gardener({
|
|
13
|
+
t: 'div',
|
|
14
|
+
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'],
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
t: 'div',
|
|
18
|
+
cn: ['bg-gray-200', 'h-15', 'text-black', 'rounded-t-lg', 'flex', 'items-center', 'justify-around'],
|
|
19
|
+
children: [
|
|
20
|
+
{
|
|
21
|
+
t: 'h3',
|
|
22
|
+
cn: ['font-bold', 'p-5'],
|
|
23
|
+
txt: 'Parser Window'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
t: 'div',
|
|
27
|
+
cn: ['flex', 'gap-3'],
|
|
28
|
+
children: [
|
|
29
|
+
{
|
|
30
|
+
t: 'button',
|
|
31
|
+
cn: ['p-2', 'bg-green-300', 'rounded-lg', 'cursor-pointer'],
|
|
32
|
+
txt: 'Copy Component',
|
|
33
|
+
attr: { id: 'copybtn' },
|
|
34
|
+
events: {
|
|
35
|
+
click: () => { navigator.clipboard.writeText(text); fetchElement('#copybtn').innerText = 'copied'; }
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
t: 'button',
|
|
40
|
+
cn: ['p-2', 'bg-red-300', 'rounded-lg', 'cursor-pointer'],
|
|
41
|
+
txt: 'Add Component',
|
|
42
|
+
events: {
|
|
43
|
+
click: () => addComponentForm(text)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
t: 'p',
|
|
52
|
+
cn: ['p-5', 'overflow-scroll'],
|
|
53
|
+
txt: text
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function addComponentForm(text) {
|
|
63
|
+
result.remove()
|
|
64
|
+
|
|
65
|
+
const compform = gardener({
|
|
66
|
+
t: 'form',
|
|
67
|
+
events: {
|
|
68
|
+
submit: (event) => {
|
|
69
|
+
event.preventDefault()
|
|
70
|
+
addComponent(text, `${fetchElement('.componentInp').value}`)
|
|
71
|
+
compform.remove();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
cn: ['fixed', 'left-2/5', 'bg-gray-500', 'rounded-lg', 'block', 'top-2/5', 'p-2'],
|
|
75
|
+
children: [
|
|
76
|
+
{
|
|
77
|
+
t: 'input',
|
|
78
|
+
cn: ['bg-white', 'componentInp'],
|
|
79
|
+
attr: {
|
|
80
|
+
type: 'text',
|
|
81
|
+
placeholder: 'Component Name'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
appendElement(body, compform);
|
|
87
|
+
|
|
88
|
+
fetchElement('.componentInp').focus();
|
|
89
|
+
//setTimeout(() => result.remove(), 500);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function addComponent(txt, path) {
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch('/addcomponent', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": 'application/json'
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({ component: generateFile(txt, path), path: `${config.componentdir}/${path}.js` })
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
throw new Error(`Failed to add component: ${res.status} ${res.statusText}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const data = await res.json()
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
console.error('Add Component Error:', err);
|
|
111
|
+
gardenerError(err.message || 'Failed to add component');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
function generateFile(obj, name) {
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
const { cleanedString, extractedList } = cleanStringAndList(obj);
|
|
121
|
+
|
|
122
|
+
if (extractedList.length === 0) return `
|
|
123
|
+
import { gardener, fetchElement, replaceElement } from '../gardener.js'
|
|
124
|
+
|
|
125
|
+
export function ${name}() {
|
|
126
|
+
return gardener(${cleanedString})
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
return `
|
|
131
|
+
import { gardener, fetchElement, replaceElement } from '../gardener.js'
|
|
132
|
+
|
|
133
|
+
export function ${name}({${extractedList}}) {
|
|
134
|
+
return gardener(${cleanedString})
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
function cleanStringAndList(input) {
|
|
141
|
+
const pattern = /\?"?(\w+)"?\?/g;
|
|
142
|
+
const vars = new Set();
|
|
143
|
+
let match;
|
|
144
|
+
|
|
145
|
+
while ((match = pattern.exec(input)) !== null) {
|
|
146
|
+
vars.add(match[1]);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Replace ?var? with "+var+" and clean up resulting empty strings or double quotes
|
|
150
|
+
const cleanedString = input
|
|
151
|
+
.replace(pattern, '"+$1+"')
|
|
152
|
+
.replace(/^""\+/, '')
|
|
153
|
+
.replace(/\+""$/, '');
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
cleanedString,
|
|
157
|
+
extractedList: [...vars].join(', ')
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -29,11 +29,24 @@ export async function Fetch(
|
|
|
29
29
|
body: JSON.stringify(body),
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error(`HTTP error! status: ${res.status}`);
|
|
34
|
+
}
|
|
33
35
|
|
|
34
36
|
return res
|
|
35
37
|
}
|
|
36
38
|
catch (err) {
|
|
37
|
-
console.
|
|
39
|
+
console.error('Fetch Error:', err);
|
|
40
|
+
|
|
41
|
+
// Import and display error using gardenerError
|
|
42
|
+
import('../gardener/errorBox.js')
|
|
43
|
+
.then(({ gardenerError }) => {
|
|
44
|
+
gardenerError(err.message || 'Network request failed');
|
|
45
|
+
})
|
|
46
|
+
.catch(() => {
|
|
47
|
+
console.error('Error component not available:', err.message);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
throw err;
|
|
38
51
|
}
|
|
39
52
|
}
|