aiplang 2.9.4 → 2.10.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/bin/aiplang.js +44 -8
- package/package.json +1 -1
- package/server/server.js +1 -1
package/bin/aiplang.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs')
|
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const http = require('http')
|
|
7
7
|
|
|
8
|
-
const VERSION = '2.
|
|
8
|
+
const VERSION = '2.10.0'
|
|
9
9
|
const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
|
|
10
10
|
const cmd = process.argv[2]
|
|
11
11
|
const args = process.argv.slice(3)
|
|
@@ -648,9 +648,15 @@ function parseBlock(line) {
|
|
|
648
648
|
const binding=line.slice(start,idx).trim().replace(/^@/,'@')
|
|
649
649
|
const content=line.slice(idx+1,line.lastIndexOf('}')).trim()
|
|
650
650
|
const em=content.match(/edit\s+(PUT|PATCH)\s+(\S+)/), dm=content.match(/delete\s+(?:DELETE\s+)?(\S+)/)
|
|
651
|
-
const
|
|
651
|
+
const fallbackM=content.match(/fallback\s*:\s*([^|]+)/)
|
|
652
|
+
const retryM=content.match(/retry\s*:\s*(\S+)/)
|
|
653
|
+
const clean=content
|
|
654
|
+
.replace(/edit\s+(PUT|PATCH)\s+\S+/g,'')
|
|
655
|
+
.replace(/delete\s+(?:DELETE\s+)?\S+/g,'')
|
|
656
|
+
.replace(/fallback\s*:[^|]+/g,'')
|
|
657
|
+
.replace(/retry\s*:\s*\S+/g,'')
|
|
652
658
|
const cols=parseCols(clean)
|
|
653
|
-
return{kind:'table',binding,cols:Array.isArray(cols)?cols:[],empty:parseEmpty(clean),editPath:em?.[2]||null,editMethod:em?.[1]||'PUT',deletePath:dm?.[1]||null,deleteKey:'id',extraClass,animate,variant,style,bg}
|
|
659
|
+
return{kind:'table',binding,cols:Array.isArray(cols)?cols:[],empty:parseEmpty(clean),editPath:em?.[2]||null,editMethod:em?.[1]||'PUT',deletePath:dm?.[1]||null,deleteKey:'id',fallback:fallbackM?.[1]?.trim()||null,retry:retryM?.[1]||null,extraClass,animate,variant,style,bg}
|
|
654
660
|
}
|
|
655
661
|
|
|
656
662
|
// ── form ────────────────────────────────────────────────────
|
|
@@ -658,12 +664,17 @@ function parseBlock(line) {
|
|
|
658
664
|
const bi=line.indexOf('{');if(bi===-1) return null
|
|
659
665
|
let head=line.slice(line.startsWith('form{')?4:5,bi).trim()
|
|
660
666
|
const content=line.slice(bi+1,line.lastIndexOf('}')).trim()
|
|
661
|
-
let action=''; const ai=head.indexOf('=>')
|
|
662
|
-
if(ai!==-1){
|
|
667
|
+
let action='', optimistic=false; const ai=head.indexOf('=>')
|
|
668
|
+
if(ai!==-1){
|
|
669
|
+
action=head.slice(ai+2).trim()
|
|
670
|
+
// Optimistic: => @list.optimistic($result)
|
|
671
|
+
if(action.includes('.optimistic(')){optimistic=true;action=action.replace('.optimistic','')}
|
|
672
|
+
head=head.slice(0,ai).trim()
|
|
673
|
+
}
|
|
663
674
|
const parts=head.trim().split(/\s+/)
|
|
664
675
|
const method=parts[0]&&['GET','POST','PUT','PATCH','DELETE'].includes(parts[0].toUpperCase())?parts[0].toUpperCase():'POST'
|
|
665
676
|
const bpath=parts[method===parts[0].toUpperCase()?1:0]||''
|
|
666
|
-
return{kind:'form',method,bpath,action,fields:parseFields(content)||[],extraClass,animate,variant,style,bg}
|
|
677
|
+
return{kind:'form',method,bpath,action,optimistic,fields:parseFields(content)||[],extraClass,animate,variant,style,bg}
|
|
667
678
|
}
|
|
668
679
|
|
|
669
680
|
// ── pricing ─────────────────────────────────────────────────
|
|
@@ -858,11 +869,33 @@ function renderBlock(b, page) {
|
|
|
858
869
|
case 'testimonial': return rTestimonial(b)
|
|
859
870
|
case 'gallery': return rGallery(b)
|
|
860
871
|
case 'raw': return (b.html||'')+'\n'
|
|
872
|
+
case 'html': return `<div class="fx-html">${b.content||''}</div>\n`
|
|
873
|
+
case 'spacer': return `<div class="fx-spacer" style="height:${esc(b.height||'2rem')}"></div>\n`
|
|
874
|
+
case 'divider': return b.label?`<div class="fx-divider"><span class="fx-divider-label">${esc(b.label)}</span></div>\n`:`<hr class="fx-hr">\n`
|
|
875
|
+
case 'badge': return `<div class="fx-badge-row"><span class="fx-badge-tag">${esc(b.content||'')}</span></div>\n`
|
|
876
|
+
case 'card': return rCardBlock(b)
|
|
877
|
+
case 'cols': return rColsBlock(b)
|
|
878
|
+
case 'each': return `<div class="fx-each fx-each-${b.variant||'list'}" data-fx-each="${esc(b.binding||'')}" data-fx-tpl="${esc(b.tpl||'')}"${b.style?` style="${b.style.replace(/,/g,';')}"`:''}>\n<div class="fx-each-empty fx-td-empty">Loading...</div></div>\n`
|
|
861
879
|
case 'if': return `<div class="fx-if-wrap" data-fx-if="${esc(b.cond)}" style="display:none"></div>\n`
|
|
862
880
|
default: return ''
|
|
863
881
|
}
|
|
864
882
|
}
|
|
865
883
|
|
|
884
|
+
function rCardBlock(b) {
|
|
885
|
+
const img=b.img?`<img src="${esc(b.img)}" class="fx-card-img" alt="${esc(b.title||'')}" loading="lazy">`:'';
|
|
886
|
+
const badge=b.badge?`<span class="fx-card-badge">${esc(b.badge)}</span>`:'';
|
|
887
|
+
const title=b.title?`<h3 class="fx-card-title">${esc(b.title)}</h3>`:'';
|
|
888
|
+
const sub=b.subtitle?`<p class="fx-card-body">${esc(b.subtitle)}</p>`:'';
|
|
889
|
+
const link=b.link?`<a href="${esc(b.link.split(':')[0])}" class="fx-card-link">${esc(b.link.split(':')[1]||'View')} →</a>`:'';
|
|
890
|
+
const bg=b.bg?` style="background:${b.bg}"`:b.style?` style="${b.style.replace(/,/g,';')}"`:''
|
|
891
|
+
return`<div class="fx-card"${bg}>${img}${badge}${title}${sub}${link}</div>\n`
|
|
892
|
+
}
|
|
893
|
+
function rColsBlock(b) {
|
|
894
|
+
const cols=(b.items||[]).map(col=>`<div class="fx-col">${col}</div>`).join('')
|
|
895
|
+
const style=b.style?` style="${b.style.replace(/,/g,';')}"`:''
|
|
896
|
+
return`<div class="fx-cols fx-cols-${b.n||2}"${style}>${cols}</div>\n`
|
|
897
|
+
}
|
|
898
|
+
|
|
866
899
|
function rNav(b) {
|
|
867
900
|
if(!b.items?.[0]) return ''
|
|
868
901
|
const it=b.items[0]
|
|
@@ -973,7 +1006,9 @@ function rTable(b) {
|
|
|
973
1006
|
const da=b.deletePath?` data-fx-delete="${esc(b.deletePath)}"`:''
|
|
974
1007
|
const at=(b.editPath||b.deletePath)?'<th class="fx-th fx-th-actions">Actions</th>':''
|
|
975
1008
|
const span=cols.length+((b.editPath||b.deletePath)?1:0)
|
|
976
|
-
|
|
1009
|
+
const fallbackAttr=b.fallback?` data-fx-fallback="${esc(b.fallback)}"`:''
|
|
1010
|
+
const retryAttr=b.retry?` data-fx-retry="${esc(b.retry)}"`:''
|
|
1011
|
+
return `<div class="fx-table-wrap"><table class="fx-table" data-fx-table="${esc(b.binding)}" data-fx-cols='${keys}' data-fx-col-map='${cm}'${ea}${da}${fallbackAttr}${retryAttr}><thead><tr>${ths}${at}</tr></thead><tbody class="fx-tbody"><tr><td colspan="${span}" class="fx-td-empty">${esc(b.empty)}</td></tr></tbody></table></div>\n`
|
|
977
1012
|
}
|
|
978
1013
|
|
|
979
1014
|
function rForm(b) {
|
|
@@ -993,7 +1028,8 @@ function rForm(b) {
|
|
|
993
1028
|
if(v==='minimal') {
|
|
994
1029
|
return `<div class="fx-form-minimal"><form data-fx-form="${esc(b.bpath)}" data-fx-method="${esc(b.method)}" data-fx-action="${esc(b.action)}">${fields}<div class="fx-form-msg"></div><button type="submit" class="fx-btn">${esc(label)}</button></form></div>\n`
|
|
995
1030
|
}
|
|
996
|
-
|
|
1031
|
+
const optAttr=b.optimistic?' data-fx-optimistic="true"':''
|
|
1032
|
+
return `<div class="fx-form-wrap"><form class="fx-form"${bgStyle}${optAttr} data-fx-form="${esc(b.bpath)}" data-fx-method="${esc(b.method)}" data-fx-action="${esc(b.action)}">${fields}<div class="fx-form-msg"></div><button type="submit" class="fx-btn">${esc(label)}</button></form></div>\n`
|
|
997
1033
|
}
|
|
998
1034
|
|
|
999
1035
|
function rBtn(b) {
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -1797,7 +1797,7 @@ async function startServer(aipFile, port = 3000) {
|
|
|
1797
1797
|
|
|
1798
1798
|
// Health
|
|
1799
1799
|
srv.addRoute('GET', '/health', (req, res) => res.json(200, {
|
|
1800
|
-
status:'ok', version:'2.
|
|
1800
|
+
status:'ok', version:'2.10.0',
|
|
1801
1801
|
models: app.models.map(m=>m.name),
|
|
1802
1802
|
routes: app.apis.length, pages: app.pages.length,
|
|
1803
1803
|
admin: app.admin?.prefix || null,
|