@kuratchi/js 0.0.15 → 0.0.16
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 +10 -8
- package/dist/cli.js +80 -47
- package/dist/compiler/api-route-pipeline.d.ts +8 -0
- package/dist/compiler/api-route-pipeline.js +23 -0
- package/dist/compiler/asset-pipeline.d.ts +7 -0
- package/dist/compiler/asset-pipeline.js +33 -0
- package/dist/compiler/client-module-pipeline.d.ts +25 -0
- package/dist/compiler/client-module-pipeline.js +257 -0
- package/dist/compiler/compiler-shared.d.ts +55 -0
- package/dist/compiler/compiler-shared.js +4 -0
- package/dist/compiler/component-pipeline.d.ts +15 -0
- package/dist/compiler/component-pipeline.js +163 -0
- package/dist/compiler/config-reading.d.ts +11 -0
- package/dist/compiler/config-reading.js +323 -0
- package/dist/compiler/convention-discovery.d.ts +9 -0
- package/dist/compiler/convention-discovery.js +83 -0
- package/dist/compiler/durable-object-pipeline.d.ts +9 -0
- package/dist/compiler/durable-object-pipeline.js +255 -0
- package/dist/compiler/error-page-pipeline.d.ts +1 -0
- package/dist/compiler/error-page-pipeline.js +16 -0
- package/dist/compiler/import-linking.d.ts +36 -0
- package/dist/compiler/import-linking.js +139 -0
- package/dist/compiler/index.d.ts +3 -3
- package/dist/compiler/index.js +133 -3305
- package/dist/compiler/layout-pipeline.d.ts +31 -0
- package/dist/compiler/layout-pipeline.js +155 -0
- package/dist/compiler/page-route-pipeline.d.ts +16 -0
- package/dist/compiler/page-route-pipeline.js +62 -0
- package/dist/compiler/parser.d.ts +4 -0
- package/dist/compiler/parser.js +433 -51
- package/dist/compiler/root-layout-pipeline.d.ts +10 -0
- package/dist/compiler/root-layout-pipeline.js +517 -0
- package/dist/compiler/route-discovery.d.ts +7 -0
- package/dist/compiler/route-discovery.js +87 -0
- package/dist/compiler/route-pipeline.d.ts +57 -0
- package/dist/compiler/route-pipeline.js +296 -0
- package/dist/compiler/route-state-pipeline.d.ts +25 -0
- package/dist/compiler/route-state-pipeline.js +139 -0
- package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
- package/dist/compiler/routes-module-feature-blocks.js +330 -0
- package/dist/compiler/routes-module-pipeline.d.ts +2 -0
- package/dist/compiler/routes-module-pipeline.js +6 -0
- package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
- package/dist/compiler/routes-module-runtime-shell.js +81 -0
- package/dist/compiler/routes-module-types.d.ts +44 -0
- package/dist/compiler/routes-module-types.js +1 -0
- package/dist/compiler/script-transform.d.ts +16 -0
- package/dist/compiler/script-transform.js +218 -0
- package/dist/compiler/server-module-pipeline.d.ts +13 -0
- package/dist/compiler/server-module-pipeline.js +124 -0
- package/dist/compiler/template.d.ts +13 -1
- package/dist/compiler/template.js +315 -58
- package/dist/compiler/worker-output-pipeline.d.ts +13 -0
- package/dist/compiler/worker-output-pipeline.js +37 -0
- package/dist/compiler/wrangler-sync.d.ts +14 -0
- package/dist/compiler/wrangler-sync.js +185 -0
- package/dist/runtime/app.js +15 -3
- package/dist/runtime/generated-worker.d.ts +33 -0
- package/dist/runtime/generated-worker.js +412 -0
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/router.d.ts +2 -1
- package/dist/runtime/router.js +12 -3
- package/dist/runtime/types.d.ts +8 -2
- package/package.json +5 -1
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
const ROOT_HEAD_SLOT = '<!--__KURATCHI_ROOT_HEAD_SLOT__-->';
|
|
2
|
+
const ROOT_BODY_SLOT = '<!--__KURATCHI_ROOT_BODY_SLOT__-->';
|
|
3
|
+
function insertBeforeClosingTag(source, tagName, marker) {
|
|
4
|
+
const lower = source.toLowerCase();
|
|
5
|
+
const closingTag = `</${tagName}>`;
|
|
6
|
+
const idx = lower.lastIndexOf(closingTag);
|
|
7
|
+
if (idx === -1) {
|
|
8
|
+
return tagName === 'head' ? `${marker}\n${source}` : `${source}\n${marker}`;
|
|
9
|
+
}
|
|
10
|
+
return source.slice(0, idx) + marker + '\n' + source.slice(idx);
|
|
11
|
+
}
|
|
12
|
+
function compactInlineJs(source) {
|
|
13
|
+
return source
|
|
14
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
15
|
+
.replace(/\n+/g, ' ')
|
|
16
|
+
.replace(/\s{2,}/g, ' ')
|
|
17
|
+
.replace(/\s*([{}();,:])\s*/g, '$1')
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
function patchHtmlTag(source, theme, radius) {
|
|
21
|
+
return source.replace(/(<html\b)([^>]*)(>)/i, (_match, open, attrs, close) => {
|
|
22
|
+
if (theme === 'dark') {
|
|
23
|
+
if (/\bclass\s*=\s*"([^"]*)"/i.test(attrs)) {
|
|
24
|
+
attrs = attrs.replace(/class\s*=\s*"([^"]*)"/i, (_classMatch, cls) => {
|
|
25
|
+
const classes = cls.split(/\s+/).filter(Boolean);
|
|
26
|
+
if (!classes.includes('dark'))
|
|
27
|
+
classes.unshift('dark');
|
|
28
|
+
return `class="${classes.join(' ')}"`;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
attrs += ' class="dark"';
|
|
33
|
+
}
|
|
34
|
+
attrs = attrs.replace(/\s*data-theme\s*=\s*"[^"]*"/i, '');
|
|
35
|
+
}
|
|
36
|
+
else if (theme === 'light') {
|
|
37
|
+
attrs = attrs.replace(/class\s*=\s*"([^"]*)"/i, (_classMatch, cls) => {
|
|
38
|
+
const classes = cls.split(/\s+/).filter(Boolean).filter((className) => className !== 'dark');
|
|
39
|
+
return classes.length ? `class="${classes.join(' ')}"` : '';
|
|
40
|
+
});
|
|
41
|
+
attrs = attrs.replace(/\s*data-theme\s*=\s*"[^"]*"/i, '');
|
|
42
|
+
}
|
|
43
|
+
else if (theme === 'system') {
|
|
44
|
+
attrs = attrs.replace(/class\s*=\s*"([^"]*)"/i, (_classMatch, cls) => {
|
|
45
|
+
const classes = cls.split(/\s+/).filter(Boolean).filter((className) => className !== 'dark');
|
|
46
|
+
return classes.length ? `class="${classes.join(' ')}"` : '';
|
|
47
|
+
});
|
|
48
|
+
if (/data-theme\s*=/i.test(attrs)) {
|
|
49
|
+
attrs = attrs.replace(/data-theme\s*=\s*"[^"]*"/i, 'data-theme="system"');
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
attrs += ' data-theme="system"';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
attrs = attrs.replace(/\s*data-radius\s*=\s*"[^"]*"/i, '');
|
|
56
|
+
if (radius === 'none' || radius === 'full') {
|
|
57
|
+
attrs += ` data-radius="${radius}"`;
|
|
58
|
+
}
|
|
59
|
+
return open + attrs + close;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const BRIDGE_SOURCE = `(function(){
|
|
63
|
+
function by(sel, root){ return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
|
|
64
|
+
var __refreshSeq = Object.create(null);
|
|
65
|
+
var __clientHandlers = Object.create(null);
|
|
66
|
+
window.__kuratchiClient = window.__kuratchiClient || {
|
|
67
|
+
register: function(routeId, handlers){
|
|
68
|
+
if(!routeId || !handlers) return;
|
|
69
|
+
__clientHandlers[String(routeId)] = Object.assign(__clientHandlers[String(routeId)] || {}, handlers);
|
|
70
|
+
},
|
|
71
|
+
invoke: function(routeId, handlerId, args, event, element){
|
|
72
|
+
var routeHandlers = __clientHandlers[String(routeId)] || null;
|
|
73
|
+
var handler = routeHandlers ? routeHandlers[String(handlerId)] : null;
|
|
74
|
+
if(typeof handler !== 'function') return;
|
|
75
|
+
return handler(Array.isArray(args) ? args : [], event, element);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function syncGroup(group){
|
|
79
|
+
var items = by('[data-select-item]').filter(function(el){ return el.getAttribute('data-select-item') === group; });
|
|
80
|
+
var masters = by('[data-select-all]').filter(function(el){ return el.getAttribute('data-select-all') === group; });
|
|
81
|
+
if(!items.length || !masters.length) return;
|
|
82
|
+
var all = items.every(function(i){ return !!i.checked; });
|
|
83
|
+
var any = items.some(function(i){ return !!i.checked; });
|
|
84
|
+
masters.forEach(function(m){ m.checked = all; m.indeterminate = any && !all; });
|
|
85
|
+
}
|
|
86
|
+
function inferQueryKey(getName, argsRaw){
|
|
87
|
+
if(!getName) return '';
|
|
88
|
+
return 'query:' + String(getName) + '|' + (argsRaw || '[]');
|
|
89
|
+
}
|
|
90
|
+
function blockKey(el){
|
|
91
|
+
if(!el || !el.getAttribute) return '';
|
|
92
|
+
var explicit = el.getAttribute('data-key');
|
|
93
|
+
if(explicit) return 'key:' + explicit;
|
|
94
|
+
var inferred = inferQueryKey(el.getAttribute('data-get'), el.getAttribute('data-get-args'));
|
|
95
|
+
if(inferred) return inferred;
|
|
96
|
+
var asName = el.getAttribute('data-as');
|
|
97
|
+
if(asName) return 'as:' + asName;
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
function escHtml(v){
|
|
101
|
+
return String(v || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
102
|
+
}
|
|
103
|
+
function setBlocksLoading(blocks){
|
|
104
|
+
blocks.forEach(function(el){
|
|
105
|
+
el.setAttribute('aria-busy','true');
|
|
106
|
+
el.setAttribute('data-kuratchi-loading','1');
|
|
107
|
+
var text = el.getAttribute('data-loading-text');
|
|
108
|
+
if(text && !el.hasAttribute('data-as')){ el.innerHTML = '<p>' + escHtml(text) + '</p>'; return; }
|
|
109
|
+
el.style.opacity = '0.6';
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function clearBlocksLoading(blocks){
|
|
113
|
+
blocks.forEach(function(el){
|
|
114
|
+
el.removeAttribute('aria-busy');
|
|
115
|
+
el.removeAttribute('data-kuratchi-loading');
|
|
116
|
+
el.style.opacity = '';
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function replaceBlocksWithKey(key){
|
|
120
|
+
if(!key || typeof DOMParser === 'undefined'){ location.reload(); return Promise.resolve(); }
|
|
121
|
+
var oldBlocks = by('[data-get]').filter(function(el){ return blockKey(el) === key; });
|
|
122
|
+
if(!oldBlocks.length){ location.reload(); return Promise.resolve(); }
|
|
123
|
+
var first = oldBlocks[0];
|
|
124
|
+
var qFn = first ? (first.getAttribute('data-get') || '') : '';
|
|
125
|
+
var qArgs = first ? String(first.getAttribute('data-get-args') || '[]') : '[]';
|
|
126
|
+
var seq = (__refreshSeq[key] || 0) + 1;
|
|
127
|
+
__refreshSeq[key] = seq;
|
|
128
|
+
setBlocksLoading(oldBlocks);
|
|
129
|
+
var headers = { 'x-kuratchi-refresh': '1' };
|
|
130
|
+
if(qFn){ headers['x-kuratchi-query-fn'] = String(qFn); headers['x-kuratchi-query-args'] = qArgs; }
|
|
131
|
+
return fetch(location.pathname + location.search, { headers: headers })
|
|
132
|
+
.then(function(r){ if(!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
133
|
+
.then(function(html){
|
|
134
|
+
if(__refreshSeq[key] !== seq) return;
|
|
135
|
+
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
136
|
+
var newBlocks = by('[data-get]', doc).filter(function(el){ return blockKey(el) === key; });
|
|
137
|
+
if(!oldBlocks.length || !newBlocks.length){ location.reload(); return; }
|
|
138
|
+
for(var i=0;i<oldBlocks.length;i++){ if(newBlocks[i]) oldBlocks[i].outerHTML = newBlocks[i].outerHTML; }
|
|
139
|
+
by('[data-select-all]').forEach(function(m){ var g=m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
140
|
+
})
|
|
141
|
+
.catch(function(){
|
|
142
|
+
if(__refreshSeq[key] === seq) clearBlocksLoading(oldBlocks);
|
|
143
|
+
location.reload();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function replaceBlocksByDescriptor(fnName, argsRaw){
|
|
147
|
+
if(!fnName || typeof DOMParser === 'undefined'){ location.reload(); return Promise.resolve(); }
|
|
148
|
+
var normalizedArgs = String(argsRaw || '[]');
|
|
149
|
+
var oldBlocks = by('[data-get]').filter(function(el){
|
|
150
|
+
return (el.getAttribute('data-get') || '') === String(fnName) &&
|
|
151
|
+
String(el.getAttribute('data-get-args') || '[]') === normalizedArgs;
|
|
152
|
+
});
|
|
153
|
+
if(!oldBlocks.length){ location.reload(); return Promise.resolve(); }
|
|
154
|
+
var key = 'fn:' + String(fnName) + '|' + normalizedArgs;
|
|
155
|
+
var seq = (__refreshSeq[key] || 0) + 1;
|
|
156
|
+
__refreshSeq[key] = seq;
|
|
157
|
+
setBlocksLoading(oldBlocks);
|
|
158
|
+
return fetch(location.pathname + location.search, {
|
|
159
|
+
headers: {
|
|
160
|
+
'x-kuratchi-refresh': '1',
|
|
161
|
+
'x-kuratchi-query-fn': String(fnName),
|
|
162
|
+
'x-kuratchi-query-args': normalizedArgs,
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
.then(function(r){ if(!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
166
|
+
.then(function(html){
|
|
167
|
+
if(__refreshSeq[key] !== seq) return;
|
|
168
|
+
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
169
|
+
var newBlocks = by('[data-get]', doc).filter(function(el){
|
|
170
|
+
return (el.getAttribute('data-get') || '') === String(fnName) &&
|
|
171
|
+
String(el.getAttribute('data-get-args') || '[]') === normalizedArgs;
|
|
172
|
+
});
|
|
173
|
+
if(!newBlocks.length){ location.reload(); return; }
|
|
174
|
+
for(var i=0;i<oldBlocks.length;i++){ if(newBlocks[i]) oldBlocks[i].outerHTML = newBlocks[i].outerHTML; }
|
|
175
|
+
by('[data-select-all]').forEach(function(m){ var g=m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
176
|
+
})
|
|
177
|
+
.catch(function(){
|
|
178
|
+
if(__refreshSeq[key] === seq) clearBlocksLoading(oldBlocks);
|
|
179
|
+
location.reload();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function refreshByDescriptor(fnName, argsRaw){
|
|
183
|
+
if(!fnName) { location.reload(); return Promise.resolve(); }
|
|
184
|
+
return replaceBlocksByDescriptor(fnName, argsRaw || '[]');
|
|
185
|
+
}
|
|
186
|
+
function refreshNearest(el){
|
|
187
|
+
var host = el && el.closest ? el.closest('[data-get]') : null;
|
|
188
|
+
if(!host){ location.reload(); return Promise.resolve(); }
|
|
189
|
+
return replaceBlocksWithKey(blockKey(host));
|
|
190
|
+
}
|
|
191
|
+
function refreshTargets(raw){
|
|
192
|
+
if(!raw){ location.reload(); return Promise.resolve(); }
|
|
193
|
+
var keys = String(raw).split(',').map(function(v){ return v.trim(); }).filter(Boolean);
|
|
194
|
+
if(!keys.length){ location.reload(); return Promise.resolve(); }
|
|
195
|
+
return Promise.all(keys.map(function(k){ return replaceBlocksWithKey('key:' + k); })).then(function(){});
|
|
196
|
+
}
|
|
197
|
+
function act(e){
|
|
198
|
+
var clientSel = '[data-client-event="' + e.type + '"]';
|
|
199
|
+
var clientEl = e.target && e.target.closest ? e.target.closest(clientSel) : null;
|
|
200
|
+
if(clientEl){
|
|
201
|
+
var routeId = clientEl.getAttribute('data-client-route') || '';
|
|
202
|
+
var handlerId = clientEl.getAttribute('data-client-handler') || '';
|
|
203
|
+
var argsRaw = clientEl.getAttribute('data-client-args') || '[]';
|
|
204
|
+
var args = [];
|
|
205
|
+
try {
|
|
206
|
+
var parsedArgs = JSON.parse(argsRaw);
|
|
207
|
+
args = Array.isArray(parsedArgs) ? parsedArgs : [];
|
|
208
|
+
} catch(_err) {}
|
|
209
|
+
try {
|
|
210
|
+
var result = window.__kuratchiClient && typeof window.__kuratchiClient.invoke === 'function'
|
|
211
|
+
? window.__kuratchiClient.invoke(routeId, handlerId, args, e, clientEl)
|
|
212
|
+
: undefined;
|
|
213
|
+
if(result === false){
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
} catch(err) {
|
|
219
|
+
console.error('[kuratchi] client handler error:', err);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if(e.type === 'click'){
|
|
223
|
+
var g = e.target && e.target.closest ? e.target.closest('[data-get]') : null;
|
|
224
|
+
if(g && !g.hasAttribute('data-as') && !g.hasAttribute('data-action')){
|
|
225
|
+
var getUrl = g.getAttribute('data-get');
|
|
226
|
+
if(getUrl){
|
|
227
|
+
if(/^[a-z][a-z0-9+\-.]*:/i.test(getUrl) && !/^https?:/i.test(getUrl)) return;
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
location.assign(getUrl);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
var r = e.target && e.target.closest ? e.target.closest('[data-refresh]') : null;
|
|
234
|
+
if(r && !r.hasAttribute('data-action')){
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
var rf = r.getAttribute('data-refresh');
|
|
237
|
+
var ra = r.getAttribute('data-refresh-args');
|
|
238
|
+
if(ra !== null){ refreshByDescriptor(rf, ra || '[]'); return; }
|
|
239
|
+
if(rf && rf.trim()){ refreshTargets(rf); return; }
|
|
240
|
+
refreshNearest(r);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
var sel = '[data-action][data-action-event="' + e.type + '"]';
|
|
245
|
+
var b = e.target && e.target.closest ? e.target.closest(sel) : null;
|
|
246
|
+
if(!b) return;
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
var fd = new FormData();
|
|
249
|
+
fd.append('_action', b.getAttribute('data-action') || '');
|
|
250
|
+
fd.append('_args', b.getAttribute('data-args') || '[]');
|
|
251
|
+
var m = b.getAttribute('data-action-method');
|
|
252
|
+
if(m) fd.append('_method', String(m).toUpperCase());
|
|
253
|
+
fetch(location.pathname, { method: 'POST', body: fd })
|
|
254
|
+
.then(function(r){
|
|
255
|
+
if(!r.ok){
|
|
256
|
+
return r.json().then(function(j){ throw new Error((j && j.error) || ('HTTP ' + r.status)); }).catch(function(){ throw new Error('HTTP ' + r.status); });
|
|
257
|
+
}
|
|
258
|
+
return r.json();
|
|
259
|
+
})
|
|
260
|
+
.then(function(j){
|
|
261
|
+
if(j && j.redirectTo){ location.assign(j.redirectTo); return; }
|
|
262
|
+
if(!b.hasAttribute('data-refresh')) return;
|
|
263
|
+
var refreshFn = b.getAttribute('data-refresh');
|
|
264
|
+
var refreshArgs = b.getAttribute('data-refresh-args');
|
|
265
|
+
if(refreshArgs !== null){ return refreshByDescriptor(refreshFn, refreshArgs || '[]'); }
|
|
266
|
+
if(refreshFn && refreshFn.trim()){ return refreshTargets(refreshFn); }
|
|
267
|
+
return refreshNearest(b);
|
|
268
|
+
})
|
|
269
|
+
.catch(function(err){ console.error('[kuratchi] client action error:', err); });
|
|
270
|
+
}
|
|
271
|
+
['click','change','input','focus','blur','submit'].forEach(function(ev){ document.addEventListener(ev, act, true); });
|
|
272
|
+
function autoLoadQueries(){
|
|
273
|
+
var seen = Object.create(null);
|
|
274
|
+
by('[data-get][data-as]').forEach(function(el){
|
|
275
|
+
var fn = el.getAttribute('data-get');
|
|
276
|
+
if(!fn) return;
|
|
277
|
+
var args = String(el.getAttribute('data-get-args') || '[]');
|
|
278
|
+
var key = String(fn) + '|' + args;
|
|
279
|
+
if(seen[key]) return;
|
|
280
|
+
seen[key] = true;
|
|
281
|
+
replaceBlocksByDescriptor(fn, args);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if(document.readyState === 'loading'){
|
|
285
|
+
document.addEventListener('DOMContentLoaded', autoLoadQueries, { once: true });
|
|
286
|
+
} else {
|
|
287
|
+
autoLoadQueries();
|
|
288
|
+
}
|
|
289
|
+
document.addEventListener('click', function(e){
|
|
290
|
+
var b = e.target && e.target.closest ? e.target.closest('[command="fill-dialog"]') : null;
|
|
291
|
+
if(!b) return;
|
|
292
|
+
var targetId = b.getAttribute('commandfor');
|
|
293
|
+
if(!targetId) return;
|
|
294
|
+
var dialog = document.getElementById(targetId);
|
|
295
|
+
if(!dialog) return;
|
|
296
|
+
var raw = b.getAttribute('data-dialog-data');
|
|
297
|
+
if(!raw) return;
|
|
298
|
+
var data;
|
|
299
|
+
try { data = JSON.parse(raw); } catch(_err) { return; }
|
|
300
|
+
Object.keys(data).forEach(function(k){
|
|
301
|
+
var inp = dialog.querySelector('[name="col_' + k + '"]');
|
|
302
|
+
if(inp){
|
|
303
|
+
inp.value = data[k] === null || data[k] === undefined ? '' : String(data[k]);
|
|
304
|
+
inp.placeholder = data[k] === null || data[k] === undefined ? 'NULL' : '';
|
|
305
|
+
}
|
|
306
|
+
var hidden = dialog.querySelector('#dialog-field-' + k);
|
|
307
|
+
if(hidden){
|
|
308
|
+
hidden.value = data[k] === null || data[k] === undefined ? '' : String(data[k]);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
var rowidInp = dialog.querySelector('[name="rowid"]');
|
|
312
|
+
if(rowidInp && data.rowid !== undefined) rowidInp.value = String(data.rowid);
|
|
313
|
+
if(typeof dialog.showModal === 'function') dialog.showModal();
|
|
314
|
+
}, true);
|
|
315
|
+
(function initPoll(){
|
|
316
|
+
function parseInterval(str){
|
|
317
|
+
if(!str) return 30000;
|
|
318
|
+
var m = str.match(/^(\\d+(?:\\.\\d+)?)(ms|s|m)?$/i);
|
|
319
|
+
if(!m) return 30000;
|
|
320
|
+
var n = parseFloat(m[1]);
|
|
321
|
+
var u = (m[2] || 's').toLowerCase();
|
|
322
|
+
if(u === 'ms') return n;
|
|
323
|
+
if(u === 'm') return n * 60000;
|
|
324
|
+
return n * 1000;
|
|
325
|
+
}
|
|
326
|
+
function bindPollEl(el){
|
|
327
|
+
if(!el || !el.getAttribute) return;
|
|
328
|
+
if(el.getAttribute('data-kuratchi-poll-bound') === '1') return;
|
|
329
|
+
var fn = el.getAttribute('data-poll');
|
|
330
|
+
if(!fn) return;
|
|
331
|
+
el.setAttribute('data-kuratchi-poll-bound', '1');
|
|
332
|
+
var pollId = el.getAttribute('data-poll-id');
|
|
333
|
+
if(!pollId) return;
|
|
334
|
+
var baseIv = parseInterval(el.getAttribute('data-interval'));
|
|
335
|
+
var maxIv = Math.min(baseIv * 10, 300000);
|
|
336
|
+
var backoff = el.getAttribute('data-backoff') !== 'false';
|
|
337
|
+
var prevHtml = el.innerHTML;
|
|
338
|
+
var currentIv = baseIv;
|
|
339
|
+
(function tick(){
|
|
340
|
+
setTimeout(function(){
|
|
341
|
+
fetch(location.pathname + location.search, { headers: { 'x-kuratchi-fragment': pollId } })
|
|
342
|
+
.then(function(r){ return r.text(); })
|
|
343
|
+
.then(function(html){
|
|
344
|
+
if(prevHtml !== html){
|
|
345
|
+
el.innerHTML = html;
|
|
346
|
+
prevHtml = html;
|
|
347
|
+
currentIv = baseIv;
|
|
348
|
+
} else if(backoff && currentIv < maxIv){
|
|
349
|
+
currentIv = Math.min(currentIv * 1.5, maxIv);
|
|
350
|
+
}
|
|
351
|
+
tick();
|
|
352
|
+
})
|
|
353
|
+
.catch(function(){ currentIv = baseIv; tick(); });
|
|
354
|
+
}, currentIv);
|
|
355
|
+
})();
|
|
356
|
+
}
|
|
357
|
+
function scan(){
|
|
358
|
+
by('[data-poll]').forEach(bindPollEl);
|
|
359
|
+
}
|
|
360
|
+
scan();
|
|
361
|
+
setInterval(scan, 500);
|
|
362
|
+
})();
|
|
363
|
+
function confirmClick(e){
|
|
364
|
+
var el = e.target && e.target.closest ? e.target.closest('[confirm]') : null;
|
|
365
|
+
if(!el) return;
|
|
366
|
+
var msg = el.getAttribute('confirm');
|
|
367
|
+
if(!msg) return;
|
|
368
|
+
if(!window.confirm(msg)){ e.preventDefault(); e.stopPropagation(); }
|
|
369
|
+
}
|
|
370
|
+
document.addEventListener('click', confirmClick, true);
|
|
371
|
+
document.addEventListener('submit', function(e){
|
|
372
|
+
var f = e.target && e.target.matches && e.target.matches('form[confirm]') ? e.target : null;
|
|
373
|
+
if(!f) return;
|
|
374
|
+
var msg = f.getAttribute('confirm');
|
|
375
|
+
if(!msg) return;
|
|
376
|
+
if(!window.confirm(msg)){ e.preventDefault(); e.stopPropagation(); }
|
|
377
|
+
}, true);
|
|
378
|
+
document.addEventListener('submit', function(e){
|
|
379
|
+
if(e.defaultPrevented) return;
|
|
380
|
+
var f = e.target;
|
|
381
|
+
if(!f || !f.querySelector) return;
|
|
382
|
+
var aInput = f.querySelector('input[name="_action"]');
|
|
383
|
+
if(!aInput) return;
|
|
384
|
+
var aName = aInput.value;
|
|
385
|
+
if(!aName) return;
|
|
386
|
+
f.setAttribute('data-action-loading', aName);
|
|
387
|
+
Array.prototype.slice.call(f.querySelectorAll('button[type="submit"],button:not([type="button"]):not([type="reset"])')).forEach(function(b){ b.disabled = true; });
|
|
388
|
+
}, true);
|
|
389
|
+
document.addEventListener('change', function(e){
|
|
390
|
+
var t = e.target;
|
|
391
|
+
if(!t || !t.getAttribute) return;
|
|
392
|
+
var gAll = t.getAttribute('data-select-all');
|
|
393
|
+
if(gAll){
|
|
394
|
+
by('[data-select-item]').filter(function(i){ return i.getAttribute('data-select-item') === gAll; }).forEach(function(i){ i.checked = !!t.checked; });
|
|
395
|
+
syncGroup(gAll);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
var gItem = t.getAttribute('data-select-item');
|
|
399
|
+
if(gItem) syncGroup(gItem);
|
|
400
|
+
}, true);
|
|
401
|
+
by('[data-select-all]').forEach(function(m){ var g = m.getAttribute('data-select-all'); if(g) syncGroup(g); });
|
|
402
|
+
})();`;
|
|
403
|
+
const REACTIVE_RUNTIME_SOURCE = `(function(g){
|
|
404
|
+
if(g.__kuratchiReactive) return;
|
|
405
|
+
const targetMap = new WeakMap();
|
|
406
|
+
const proxyMap = new WeakMap();
|
|
407
|
+
let active = null;
|
|
408
|
+
const queue = new Set();
|
|
409
|
+
let flushing = false;
|
|
410
|
+
function queueRun(fn){
|
|
411
|
+
queue.add(fn);
|
|
412
|
+
if(flushing) return;
|
|
413
|
+
flushing = true;
|
|
414
|
+
Promise.resolve().then(function(){
|
|
415
|
+
try {
|
|
416
|
+
const jobs = Array.from(queue);
|
|
417
|
+
queue.clear();
|
|
418
|
+
for (const job of jobs) job();
|
|
419
|
+
} finally {
|
|
420
|
+
flushing = false;
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
function cleanup(effect){
|
|
425
|
+
const deps = effect.__deps || [];
|
|
426
|
+
for (const dep of deps) dep.delete(effect);
|
|
427
|
+
effect.__deps = [];
|
|
428
|
+
}
|
|
429
|
+
function track(target, key){
|
|
430
|
+
if(!active) return;
|
|
431
|
+
let depsMap = targetMap.get(target);
|
|
432
|
+
if(!depsMap){ depsMap = new Map(); targetMap.set(target, depsMap); }
|
|
433
|
+
let dep = depsMap.get(key);
|
|
434
|
+
if(!dep){ dep = new Set(); depsMap.set(key, dep); }
|
|
435
|
+
if(dep.has(active)) return;
|
|
436
|
+
dep.add(active);
|
|
437
|
+
if(!active.__deps) active.__deps = [];
|
|
438
|
+
active.__deps.push(dep);
|
|
439
|
+
}
|
|
440
|
+
function trigger(target, key){
|
|
441
|
+
const depsMap = targetMap.get(target);
|
|
442
|
+
if(!depsMap) return;
|
|
443
|
+
const effects = new Set();
|
|
444
|
+
const add = function(k){
|
|
445
|
+
const dep = depsMap.get(k);
|
|
446
|
+
if(dep) dep.forEach(function(e){ effects.add(e); });
|
|
447
|
+
};
|
|
448
|
+
add(key);
|
|
449
|
+
add('*');
|
|
450
|
+
effects.forEach(function(e){ queueRun(e); });
|
|
451
|
+
}
|
|
452
|
+
function isObject(value){ return value !== null && typeof value === 'object'; }
|
|
453
|
+
function proxify(value){
|
|
454
|
+
if(!isObject(value)) return value;
|
|
455
|
+
if(proxyMap.has(value)) return proxyMap.get(value);
|
|
456
|
+
const proxy = new Proxy(value, {
|
|
457
|
+
get(target, key, receiver){
|
|
458
|
+
track(target, key);
|
|
459
|
+
const out = Reflect.get(target, key, receiver);
|
|
460
|
+
return isObject(out) ? proxify(out) : out;
|
|
461
|
+
},
|
|
462
|
+
set(target, key, next, receiver){
|
|
463
|
+
const prev = target[key];
|
|
464
|
+
const result = Reflect.set(target, key, next, receiver);
|
|
465
|
+
if(prev !== next) trigger(target, key);
|
|
466
|
+
if(Array.isArray(target) && key !== 'length') trigger(target, 'length');
|
|
467
|
+
return result;
|
|
468
|
+
},
|
|
469
|
+
deleteProperty(target, key){
|
|
470
|
+
const had = Object.prototype.hasOwnProperty.call(target, key);
|
|
471
|
+
const result = Reflect.deleteProperty(target, key);
|
|
472
|
+
if(had) trigger(target, key);
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
proxyMap.set(value, proxy);
|
|
477
|
+
return proxy;
|
|
478
|
+
}
|
|
479
|
+
function effect(fn){
|
|
480
|
+
const run = function(){
|
|
481
|
+
cleanup(run);
|
|
482
|
+
active = run;
|
|
483
|
+
try { fn(); } finally { active = null; }
|
|
484
|
+
};
|
|
485
|
+
run.__deps = [];
|
|
486
|
+
run();
|
|
487
|
+
return function(){ cleanup(run); };
|
|
488
|
+
}
|
|
489
|
+
function state(initial){ return proxify(initial); }
|
|
490
|
+
function replace(_prev, next){ return proxify(next); }
|
|
491
|
+
g.__kuratchiReactive = { state, effect, replace };
|
|
492
|
+
})(window);`;
|
|
493
|
+
export function prepareRootLayoutSource(opts) {
|
|
494
|
+
let source = opts.source;
|
|
495
|
+
const headInjections = [];
|
|
496
|
+
const bodyInjections = [];
|
|
497
|
+
if (opts.uiConfigValues) {
|
|
498
|
+
source = patchHtmlTag(source, opts.uiConfigValues.theme, opts.uiConfigValues.radius);
|
|
499
|
+
}
|
|
500
|
+
if (opts.uiConfigValues) {
|
|
501
|
+
const themeInitScript = `<script>(function(){try{var d=document.documentElement;var s=localStorage.getItem('kui-theme');var fallback=d.getAttribute('data-theme')==='system'?'system':(d.classList.contains('dark')?'dark':'light');var p=(s==='light'||s==='dark'||s==='system')?s:fallback;d.classList.remove('dark');d.removeAttribute('data-theme');if(p==='dark'){d.classList.add('dark');}else if(p==='system'){d.setAttribute('data-theme','system');}}catch(e){}})()</script>`;
|
|
502
|
+
headInjections.push(themeInitScript);
|
|
503
|
+
}
|
|
504
|
+
if (opts.themeCss) {
|
|
505
|
+
headInjections.push(`<style>${opts.themeCss}</style>`);
|
|
506
|
+
}
|
|
507
|
+
headInjections.push(`<style>@view-transition { navigation: auto; }</style>`);
|
|
508
|
+
const actionScript = `<script>${opts.isDev ? BRIDGE_SOURCE : compactInlineJs(BRIDGE_SOURCE)}</script>`;
|
|
509
|
+
const reactiveRuntimeScript = `<script>${opts.isDev ? REACTIVE_RUNTIME_SOURCE : compactInlineJs(REACTIVE_RUNTIME_SOURCE)}</script>`;
|
|
510
|
+
headInjections.push(reactiveRuntimeScript);
|
|
511
|
+
bodyInjections.push(actionScript);
|
|
512
|
+
source = insertBeforeClosingTag(source, 'head', ROOT_HEAD_SLOT);
|
|
513
|
+
source = insertBeforeClosingTag(source, 'body', ROOT_BODY_SLOT);
|
|
514
|
+
source = source.replace(ROOT_HEAD_SLOT, headInjections.join('\n'));
|
|
515
|
+
source = source.replace(ROOT_BODY_SLOT, bodyInjections.join('\n'));
|
|
516
|
+
return source;
|
|
517
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
export function discoverRoutes(routesDir) {
|
|
4
|
+
const results = [];
|
|
5
|
+
const registered = new Set();
|
|
6
|
+
function getLayoutsForPrefix(prefix) {
|
|
7
|
+
const layouts = [];
|
|
8
|
+
if (fs.existsSync(path.join(routesDir, 'layout.html')))
|
|
9
|
+
layouts.push('layout.html');
|
|
10
|
+
if (!prefix)
|
|
11
|
+
return layouts;
|
|
12
|
+
const parts = prefix.split('/').filter(Boolean);
|
|
13
|
+
let current = '';
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
current = current ? `${current}/${part}` : part;
|
|
16
|
+
const rel = `${current}/layout.html`;
|
|
17
|
+
if (fs.existsSync(path.join(routesDir, rel)))
|
|
18
|
+
layouts.push(rel);
|
|
19
|
+
}
|
|
20
|
+
return layouts;
|
|
21
|
+
}
|
|
22
|
+
function walk(dir, prefix) {
|
|
23
|
+
if (!fs.existsSync(dir))
|
|
24
|
+
return;
|
|
25
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
const childPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
29
|
+
const pageFile = path.join(dir, entry.name, 'page.html');
|
|
30
|
+
if (fs.existsSync(pageFile)) {
|
|
31
|
+
const routeFile = `${childPrefix}/page.html`;
|
|
32
|
+
if (!registered.has(routeFile)) {
|
|
33
|
+
registered.add(routeFile);
|
|
34
|
+
results.push({ file: routeFile, name: childPrefix, layouts: getLayoutsForPrefix(childPrefix), type: 'page' });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const apiFile = ['index.ts', 'index.js'].find((fileName) => fs.existsSync(path.join(dir, entry.name, fileName)));
|
|
38
|
+
if (apiFile && !fs.existsSync(pageFile)) {
|
|
39
|
+
const routeFile = `${childPrefix}/${apiFile}`;
|
|
40
|
+
if (!registered.has(routeFile)) {
|
|
41
|
+
registered.add(routeFile);
|
|
42
|
+
results.push({ file: routeFile, name: childPrefix, layouts: [], type: 'api' });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
walk(path.join(dir, entry.name), childPrefix);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (entry.name === 'layout.html' || entry.name === '404.html' || entry.name === '500.html') {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (entry.name === 'index.ts' || entry.name === 'index.js') {
|
|
52
|
+
const routeFile = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
53
|
+
if (!registered.has(routeFile)) {
|
|
54
|
+
registered.add(routeFile);
|
|
55
|
+
results.push({ file: routeFile, name: prefix || 'index', layouts: [], type: 'api' });
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (entry.name === 'page.html') {
|
|
60
|
+
const routeFile = prefix ? `${prefix}/page.html` : 'page.html';
|
|
61
|
+
if (!registered.has(routeFile)) {
|
|
62
|
+
registered.add(routeFile);
|
|
63
|
+
results.push({ file: routeFile, name: prefix || 'index', layouts: getLayoutsForPrefix(prefix), type: 'page' });
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (entry.name.endsWith('.html') && entry.name !== 'page.html') {
|
|
68
|
+
const name = prefix
|
|
69
|
+
? `${prefix}/${entry.name.replace('.html', '')}`
|
|
70
|
+
: entry.name.replace('.html', '');
|
|
71
|
+
results.push({
|
|
72
|
+
file: prefix ? `${prefix}/${entry.name}` : entry.name,
|
|
73
|
+
name,
|
|
74
|
+
layouts: getLayoutsForPrefix(prefix),
|
|
75
|
+
type: 'page',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
walk(routesDir, '');
|
|
81
|
+
results.sort((a, b) => {
|
|
82
|
+
const aScore = a.name.includes('[...') ? 2 : a.name.includes('[') ? 1 : 0;
|
|
83
|
+
const bScore = b.name.includes('[...') ? 2 : b.name.includes('[') ? 1 : 0;
|
|
84
|
+
return aScore - bScore || a.name.localeCompare(b.name);
|
|
85
|
+
});
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ParsedFile } from './parser.js';
|
|
2
|
+
import { type RouteScriptSegment } from './script-transform.js';
|
|
3
|
+
export interface RouteDataGetQuery {
|
|
4
|
+
fnName: string;
|
|
5
|
+
argsExpr: string;
|
|
6
|
+
asName: string;
|
|
7
|
+
key?: string;
|
|
8
|
+
rpcId?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RouteActionBinding {
|
|
11
|
+
name: string;
|
|
12
|
+
expression: string;
|
|
13
|
+
}
|
|
14
|
+
export interface RouteRpcBinding {
|
|
15
|
+
name: string;
|
|
16
|
+
rpcId: string;
|
|
17
|
+
expression: string;
|
|
18
|
+
}
|
|
19
|
+
export interface RouteLoadPlan {
|
|
20
|
+
mode: 'none' | 'explicit' | 'generated';
|
|
21
|
+
code: string;
|
|
22
|
+
returnVars: string[];
|
|
23
|
+
scriptUsesAwait: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface RouteRenderPlan {
|
|
26
|
+
prelude: string;
|
|
27
|
+
dataVars: string[];
|
|
28
|
+
body: string;
|
|
29
|
+
headBody: string;
|
|
30
|
+
componentStyles: string[];
|
|
31
|
+
clientModuleHref?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface RouteBuildPlan {
|
|
34
|
+
pattern: string;
|
|
35
|
+
load: RouteLoadPlan;
|
|
36
|
+
actions: RouteActionBinding[];
|
|
37
|
+
rpc: RouteRpcBinding[];
|
|
38
|
+
render: RouteRenderPlan;
|
|
39
|
+
}
|
|
40
|
+
export interface RoutePipelineParsedFile extends ParsedFile {
|
|
41
|
+
scriptImportDecls?: string[];
|
|
42
|
+
scriptSegments?: RouteScriptSegment[];
|
|
43
|
+
}
|
|
44
|
+
interface AnalyzeRouteOptions {
|
|
45
|
+
pattern: string;
|
|
46
|
+
renderBody: string;
|
|
47
|
+
renderHeadBody: string;
|
|
48
|
+
isDev: boolean;
|
|
49
|
+
parsed: RoutePipelineParsedFile;
|
|
50
|
+
fnToModule: Record<string, string>;
|
|
51
|
+
rpcNameMap?: Map<string, string>;
|
|
52
|
+
componentStyles: string[];
|
|
53
|
+
clientModuleHref?: string | null;
|
|
54
|
+
}
|
|
55
|
+
export declare function analyzeRouteBuild(opts: AnalyzeRouteOptions): RouteBuildPlan;
|
|
56
|
+
export declare function emitRouteObject(plan: RouteBuildPlan): string;
|
|
57
|
+
export {};
|