Siteβ―Builder
Editing:
index.php
writable 0666
<?php /* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ NavβMaker 0.9 β build JSON nav files for MelanieΒ AI sites Place in /site/navβbuilder.php (writes to /site/parts/nav/*.json) ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ /* βΌ plug in your own auth here later */ // include __DIR__.'/my-security.php'; define('SITE_BASE', realpath(__DIR__)); define('NAV_DIR', SITE_BASE.'/parts/nav'); define('MAX_DEPTH', 6); // how deep to scan folders if (!is_dir(NAV_DIR)) mkdir(NAV_DIR, 0755, true); /* ---------- helpers ---------- */ function clean($s){ return trim(str_replace(['..','\\'],['','/'],$s),'/'); } function listTree($base,$depth=0){ if($depth>MAX_DEPTH) return ''; $out=''; foreach(scandir($base) as $item){ if($item[0]==='.') continue; $full="$base/$item"; if(is_dir($full)){ $out.='<details><summary>'.htmlspecialchars($item).'</summary>'; $out.=listTree($full,$depth+1); $out.='</details>'; }else{ if(!preg_match('/\.(html?|php|md|json|txt)$/i',$item)) continue; $rel=str_replace(SITE_BASE.'/','',$full); $out.='<label class="file"><input type="checkbox" data-path="'.$rel.'"> '.htmlspecialchars($item).'</label>'; } } return $out; } function navPath($name){ return NAV_DIR.'/'.preg_replace('/[^a-z0-9_\-]/i','',$name).'.json'; } /* ---------- routing ---------- */ $menu = $_GET['menu'] ?? ($_POST['menu'] ?? ''); $action = $_POST['action'] ?? ''; if($action==='save'){ $json = $_POST['json'] ?? '[]'; file_put_contents(navPath($menu), $json); die('ok'); } /* ---------- load existing nav ---------- */ $items = []; if($menu && file_exists(navPath($menu))){ $items = json_decode(file_get_contents(navPath($menu)),true) ?? []; } /* ---------- UI ---------- */ ?><!DOCTYPE html> <html lang="en"><head><meta charset="utf-8"> <title>NavβMaker</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> :root{ --bg:#101119;--panel:#1c1d27;--accent:#ff4ecd;--fg:#e8e8ee; --radius:12px;font-family:system-ui,sans-serif;color:var(--fg); } body{margin:0;background:var(--bg);display:flex;flex-direction:column;min-height:100vh} h1{font-size:1.5rem;padding:1rem 2rem;margin:0;background:var(--panel)} main{flex:1;display:grid;grid-template-columns:320px 1fr;gap:2rem;padding:2rem} section{background:var(--panel);padding:1.2rem;border-radius:var(--radius);overflow:auto} label.file{display:block;cursor:pointer;margin:.25rem 0} #builder li{list-style:none;background:#2b2c3b;margin:.3rem 0;padding:.5rem;border-radius:8px;display:flex;gap:.6rem;align-items:center} #builder input[type=text]{flex:1 1 auto;border:none;background:transparent;color:inherit} #builder input[type=text]:focus{outline:1px solid var(--accent)} #builder button{background:var(--accent);color:#fff;border:none;padding:.3rem .6rem;border-radius:6px;cursor:pointer} #builder li.dragging{opacity:.5} button.big{padding:.9rem 1.6rem;font-weight:600;border-radius:40px;background:var(--accent);border:none;color:#fff;font-size:1rem;cursor:pointer;margin-top:1rem} </style></head><body> <h1>NavβMaker <?= $menu? 'Β· '.htmlspecialchars($menu):'' ?></h1> <main> <!-- left panel --> <section> <h2 style="margin-top:0;font-size:1.2rem">1β―Β·β―Menu name</h2> <form method="get"> <input name="menu" placeholder="primary, footerβ¦" value="<?=htmlspecialchars($menu)?>" style="width:100%;padding:.5rem;border-radius:8px;border:none;margin-bottom:1rem"> <button class="big" style="width:100%">Create / Load</button> </form> <?php if($menu): ?> <hr style="border:none;border-top:1px solid #333;margin:1.2rem 0"> <h2 style="font-size:1.1rem">2β―Β·β―Pick pages</h2> <div style="max-height:45vh;overflow:auto"> <?=listTree(SITE_BASE)?> </div> <?php endif; ?> </section> <?php if($menu): ?> <!-- right panel --> <section> <h2 style="margin-top:0;font-size:1.2rem">3β―Β·β―Arrange & edit</h2> <ul id="builder"><?php foreach($items as $i){ echo '<li draggable="true"><span class="drag">β</span>'. '<input type="text" class="label" value="'.htmlspecialchars($i['label']).'">'. '<input type="text" class="path" value="'.htmlspecialchars($i['path']).'">'. '<label><input type="checkbox" class="active" '.($i['active']?'checked':'').'>active</label>'. '<button class="del">β</button></li>'; } ?></ul> <button id="save" class="big">Save menu</button> <p id="status" style="opacity:.7;margin-top:.8rem"></p> <template id="tmpl"> <li draggable="true"><span class="drag">β</span> <input type="text" class="label" value=""> <input type="text" class="path" value=""> <label><input type="checkbox" class="active" checked>active</label> <button class="del">β</button></li> </template> </section> <?php endif; ?> </main> <?php if($menu): ?> <script> const tmpl = document.getElementById('tmpl').content; const ul = document.getElementById('builder'); /* ------- fileβtree β add item ------- */ document.querySelectorAll('label.file input[type=checkbox]').forEach(cb=>{ cb.addEventListener('change',e=>{ if(!cb.checked) return; const li = tmpl.cloneNode(true); li.querySelector('.label').value = cb.parentNode.textContent.trim(); li.querySelector('.path').value = cb.dataset.path; ul.append(li); }); }); /* ------- delete ------- */ ul.addEventListener('click',e=>{ if(e.target.classList.contains('del')) e.target.closest('li').remove(); }); /* ------- dragβreorder ------- */ let dragItem=null; ul.addEventListener('dragstart',e=>{ dragItem=e.target; e.dataTransfer.effectAllowed='move'; setTimeout(()=>dragItem.classList.add('dragging'),0); }); ul.addEventListener('dragend',()=>dragItem.classList.remove('dragging')); ul.addEventListener('dragover',e=>{ e.preventDefault(); const after = [...ul.querySelectorAll('li:not(.dragging)')].find(li=>e.clientY<li.offsetTop+li.offsetHeight/2); ul.insertBefore(dragItem, after); }); /* ------- save ------- */ document.getElementById('save').onclick=()=>{ const list=[...ul.children].map(li=>({ label : li.querySelector('.label').value.trim()||'', path : li.querySelector('.path').value.trim(), active: li.querySelector('.active').checked })); fetch('',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:new URLSearchParams({action:'save',menu:'<?=htmlspecialchars($menu)?>',json:JSON.stringify(list)})}) .then(r=>r.text()).then(t=>document.getElementById('status').textContent=t==='ok'?'β Saved':'Error'); }; </script> <?php endif; ?> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel