aiplang 2.7.2 → 2.7.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/bin/aiplang.js +27 -16
- 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.7.
|
|
8
|
+
const VERSION = '2.7.4'
|
|
9
9
|
const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
|
|
10
10
|
const cmd = process.argv[2]
|
|
11
11
|
const args = process.argv.slice(3)
|
|
@@ -615,13 +615,15 @@ function parseBlock(line) {
|
|
|
615
615
|
}
|
|
616
616
|
|
|
617
617
|
// ── table ───────────────────────────────────────────────────
|
|
618
|
-
if(line.startsWith('table ')) {
|
|
618
|
+
if(line.startsWith('table ') || line.startsWith('table{')) {
|
|
619
619
|
const idx=line.indexOf('{');if(idx===-1) return null
|
|
620
|
-
const
|
|
620
|
+
const start=line.startsWith('table{')?6:6
|
|
621
|
+
const binding=line.slice(start,idx).trim().replace(/^@/,'@')
|
|
621
622
|
const content=line.slice(idx+1,line.lastIndexOf('}')).trim()
|
|
622
623
|
const em=content.match(/edit\s+(PUT|PATCH)\s+(\S+)/), dm=content.match(/delete\s+(?:DELETE\s+)?(\S+)/)
|
|
623
624
|
const clean=content.replace(/edit\s+(PUT|PATCH)\s+\S+/g,'').replace(/delete\s+(?:DELETE\s+)?\S+/g,'')
|
|
624
|
-
|
|
625
|
+
const cols=parseCols(clean)
|
|
626
|
+
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}
|
|
625
627
|
}
|
|
626
628
|
|
|
627
629
|
// ── form ────────────────────────────────────────────────────
|
|
@@ -727,19 +729,27 @@ function applyMods(html, b) {
|
|
|
727
729
|
|
|
728
730
|
function renderPage(page, allPages) {
|
|
729
731
|
const needsJS=page.queries.length>0||page.blocks.some(b=>['table','list','form','if','btn','select','faq'].includes(b.kind))
|
|
730
|
-
const body=page.blocks.map(b=>applyMods(renderBlock(b,page),b)).join('')
|
|
732
|
+
const body=page.blocks.map(b=>{try{return applyMods(renderBlock(b,page),b)}catch(e){console.error('[aiplang] Block render error:',b.kind,e.message);return ''}}).join('')
|
|
731
733
|
const config=needsJS?JSON.stringify({id:page.id,theme:page.theme,routes:allPages.map(p=>p.route),state:page.state,queries:page.queries}):''
|
|
732
734
|
const hydrate=needsJS?`\n<script>window.__AIPLANG_PAGE__=${config};</script>\n<script src="./aiplang-hydrate.js" defer></script>`:''
|
|
733
735
|
const customVars=page.customTheme?genCustomThemeVars(page.customTheme):''
|
|
734
736
|
const themeVarCSS=page.themeVars?genThemeVarCSS(page.themeVars):''
|
|
737
|
+
// Extract app name from nav brand if available
|
|
738
|
+
const _navBlock = page.blocks.find(b=>b.kind==='nav')
|
|
739
|
+
const _brand = _navBlock?.brand || ''
|
|
740
|
+
const _title = _brand ? `${esc(_brand)} — ${esc(page.id.charAt(0).toUpperCase()+page.id.slice(1))}` : esc(page.id.charAt(0).toUpperCase()+page.id.slice(1))
|
|
735
741
|
return `<!DOCTYPE html>
|
|
736
742
|
<html lang="en">
|
|
737
743
|
<head>
|
|
738
744
|
<meta charset="UTF-8">
|
|
739
745
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
740
|
-
<title>${
|
|
746
|
+
<title>${_title}</title>
|
|
741
747
|
<link rel="canonical" href="${esc(page.route)}">
|
|
742
748
|
<meta name="robots" content="index,follow">
|
|
749
|
+
<meta name="description" content="${esc((()=>{const h=page.blocks.find(b=>b.kind==='hero');if(!h)return '';const m=h.rawLine&&h.rawLine.match(/\{([^}]+)\}/);if(!m)return '';const p=m[1].split('>');const s=p[0].split('|')[1];return s?s.trim():''})())}">
|
|
750
|
+
<meta property="og:title" content="${_title}">
|
|
751
|
+
<meta property="og:type" content="website">
|
|
752
|
+
<meta property="og:type" content="website">
|
|
743
753
|
<style>${css(page.theme)}${customVars}${themeVarCSS}</style>
|
|
744
754
|
</head>
|
|
745
755
|
<body>
|
|
@@ -800,7 +810,7 @@ function rStats(b) {
|
|
|
800
810
|
}
|
|
801
811
|
|
|
802
812
|
function rRow(b) {
|
|
803
|
-
const cards=b.items.map(item=>{
|
|
813
|
+
const cards=(b.items||[]).map(item=>{
|
|
804
814
|
const inner=item.map((f,fi)=>{
|
|
805
815
|
if(f.isImg) return`<img src="${esc(f.src)}" class="fx-card-img" alt="" loading="lazy">`
|
|
806
816
|
if(f.isLink) return`<a href="${esc(f.path)}" class="fx-card-link">${esc(f.label)} →</a>`
|
|
@@ -825,7 +835,7 @@ function rSect(b) {
|
|
|
825
835
|
|
|
826
836
|
function rFoot(b) {
|
|
827
837
|
let inner=''
|
|
828
|
-
for(const item of b.items) for(const f of item){
|
|
838
|
+
for(const item of (b.items||[])) for(const f of item){
|
|
829
839
|
if(f.isLink) inner+=`<a href="${esc(f.path)}" class="fx-footer-link">${esc(f.label)}</a>`
|
|
830
840
|
else inner+=`<p class="fx-footer-text">${esc(f.text)}</p>`
|
|
831
841
|
}
|
|
@@ -833,13 +843,14 @@ function rFoot(b) {
|
|
|
833
843
|
}
|
|
834
844
|
|
|
835
845
|
function rTable(b) {
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
const
|
|
846
|
+
const cols=Array.isArray(b.cols)?b.cols:[]
|
|
847
|
+
const ths=cols.map(c=>`<th class="fx-th">${esc(c.label)}</th>`).join('')
|
|
848
|
+
const keys=JSON.stringify(cols.map(c=>c.key))
|
|
849
|
+
const cm=JSON.stringify(cols.map(c=>({label:c.label,key:c.key})))
|
|
839
850
|
const ea=b.editPath?` data-fx-edit="${esc(b.editPath)}" data-fx-edit-method="${esc(b.editMethod)}"`:''
|
|
840
851
|
const da=b.deletePath?` data-fx-delete="${esc(b.deletePath)}"`:''
|
|
841
852
|
const at=(b.editPath||b.deletePath)?'<th class="fx-th fx-th-actions">Actions</th>':''
|
|
842
|
-
const span=
|
|
853
|
+
const span=cols.length+((b.editPath||b.deletePath)?1:0)
|
|
843
854
|
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}><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`
|
|
844
855
|
}
|
|
845
856
|
|
|
@@ -862,12 +873,12 @@ function rBtn(b) {
|
|
|
862
873
|
}
|
|
863
874
|
|
|
864
875
|
function rSelectBlock(b) {
|
|
865
|
-
const opts=b.options.map(o=>`<option value="${esc(o)}">${esc(o)}</option>`).join('')
|
|
876
|
+
const opts=(b.options||[]).map(o=>`<option value="${esc(o)}">${esc(o)}</option>`).join('')
|
|
866
877
|
return `<div class="fx-select-wrap"><select class="fx-input fx-select-block" data-fx-model="${esc(b.binding)}">${opts}</select></div>\n`
|
|
867
878
|
}
|
|
868
879
|
|
|
869
880
|
function rPricing(b) {
|
|
870
|
-
const cards=b.plans.map((p,i)=>{
|
|
881
|
+
const cards=(b.plans||[]).map((p,i)=>{
|
|
871
882
|
let lh='#',ll='Get started'
|
|
872
883
|
if(p.linkRaw){const m=p.linkRaw.match(/\/([^:]+):(.+)/);if(m){lh='/'+m[1];ll=m[2]}}
|
|
873
884
|
const f=i===1?' fx-pricing-featured':''
|
|
@@ -878,7 +889,7 @@ function rPricing(b) {
|
|
|
878
889
|
}
|
|
879
890
|
|
|
880
891
|
function rFaq(b) {
|
|
881
|
-
const items=b.items.map(i=>`<div class="fx-faq-item" onclick="this.classList.toggle('open')"><div class="fx-faq-q">${esc(i.q)}<span class="fx-faq-arrow">▸</span></div><div class="fx-faq-a">${esc(i.a)}</div></div>`).join('')
|
|
892
|
+
const items=(b.items||[]).map(i=>`<div class="fx-faq-item" onclick="this.classList.toggle('open')"><div class="fx-faq-q">${esc(i.q)}<span class="fx-faq-arrow">▸</span></div><div class="fx-faq-a">${esc(i.a)}</div></div>`).join('')
|
|
882
893
|
return `<section class="fx-sect"><div class="fx-faq">${items}</div></section>\n`
|
|
883
894
|
}
|
|
884
895
|
|
|
@@ -888,7 +899,7 @@ function rTestimonial(b) {
|
|
|
888
899
|
}
|
|
889
900
|
|
|
890
901
|
function rGallery(b) {
|
|
891
|
-
const imgs=b.imgs.map(src=>`<div class="fx-gallery-item"><img src="${esc(src)}" alt="" loading="lazy"></div>`).join('')
|
|
902
|
+
const imgs=(b.imgs||[]).map(src=>`<div class="fx-gallery-item"><img src="${esc(src)}" alt="" loading="lazy"></div>`).join('')
|
|
892
903
|
return `<div class="fx-gallery">${imgs}</div>\n`
|
|
893
904
|
}
|
|
894
905
|
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -1435,7 +1435,7 @@ async function startServer(aipFile, port = 3000) {
|
|
|
1435
1435
|
|
|
1436
1436
|
// Health
|
|
1437
1437
|
srv.addRoute('GET', '/health', (req, res) => res.json(200, {
|
|
1438
|
-
status:'ok', version:'2.7.
|
|
1438
|
+
status:'ok', version:'2.7.4',
|
|
1439
1439
|
models: app.models.map(m=>m.name),
|
|
1440
1440
|
routes: app.apis.length, pages: app.pages.length,
|
|
1441
1441
|
admin: app.admin?.prefix || null,
|