Siteβ―Builder
Editing:
nav-builder.php
writable 0666
<?php /* NavβMaker 1.3 β multiβsite, colourβaware Adds perβmenu lineβcolour (saved in JSON) βββββββββββββββββββββββββββββββββββββββββ */ /* ββ security gate βββββββββββββββββββββββββ */ require_once $_SERVER['DOCUMENT_ROOT'].'/members/lib/auth.php'; require_login(); if (current_user()['role']!=='admin') forbidden_page(); /* ββ CONFIG ββββββββββββββββββββββββββββββββ */ define('ROOT_PATTERN','/^mai-[a-z0-9\-]+$/'); define('MAX_DEPTH',6); define('ALLOW_EXT','/\.(html?|php|md|txt|json)$/i'); $PUBLIC_ROOT = realpath($_SERVER['DOCUMENT_ROOT']) ?: __DIR__; /* ββ find site roots βββββββββββββββββββββββ */ $allRoots=array_map('basename',glob($PUBLIC_ROOT.'/mai-*',GLOB_ONLYDIR|GLOB_NOSORT)); if(is_dir($PUBLIC_ROOT.'/site')) $allRoots[]='site'; $auto=''; for($p=__DIR__;$p&&$p!==$PUBLIC_ROOT;$p=dirname($p)) if(preg_match(ROOT_PATTERN,basename($p))){$auto=basename($p);break;} sort($allRoots,SORT_NATURAL|SORT_FLAG_CASE); /* ββ params ββββββββββββββββββββββββββββββββ */ $rootName=$_REQUEST['root'] ?? ($auto ?: ($allRoots[0]??'')); $rootPath=realpath($PUBLIC_ROOT.'/'.$rootName) ?: $PUBLIC_ROOT; $menu =$_REQUEST['menu'] ?? ''; $action =$_POST['action'] ?? ''; /* ββ helpers βββββββββββββββββββββββββββββββ */ function listTree(string $base,int $d=0):string{ if($d>MAX_DEPTH) return ''; $out=''; foreach(scandir($base) as $f){ if($f[0]==='.') continue; $full="$base/$f"; if(is_dir($full)) $out.='<details><summary>'.htmlspecialchars($f).'</summary>'.listTree($full,$d+1)."</details>"; elseif(preg_match(ALLOW_EXT,$f)){ $rel=substr($full,strlen(realpath($GLOBALS['rootPath']))+1); $out.='<label class="file"><input type="checkbox" data-path="'.htmlspecialchars($rel).'"> '.htmlspecialchars($f).'</label>'; } } return $out; } function navDir(){ $p=$GLOBALS['rootPath'].'/parts/nav'; if(!is_dir($p)) mkdir($p,0755,true); return $p; } function navFile($m){ return navDir().'/'.preg_replace('/[^a-z0-9_\-]/i','',$m).'.json'; } function existing_menus(){ $f=glob(navDir().'/*.json')?:[]; return array_map(fn($x)=>basename($x,'.json'),$f); } /* ββ SAVE ajax βββββββββββββββββββββββββββββ */ if($action==='save'){ $payload=[ 'color'=>$_POST['color'] ?? '#ff4ecd', 'items'=>json_decode($_POST['json']??'[]',true) ]; file_put_contents(navFile($_POST['menu']??'menu'),json_encode($payload,JSON_PRETTY_PRINT)); exit('ok'); } /* ββ load selected menu (if any) βββββββββββ */ $color='#ff4ecd'; $items=[]; if($menu && file_exists(navFile($menu))){ $data=json_decode(file_get_contents(navFile($menu)),true)??[]; if(isset($data['items'])){ // new structure $color=$data['color']??$color; $items=$data['items']; }else $items=$data; // legacy array } ?><!DOCTYPE html><html lang="en"><head><meta charset="utf-8"> <title>NavβMaker β <?=htmlspecialchars($rootName)?></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{margin:0;padding:1rem 2rem;background:var(--panel);font-size:1.5rem} main{flex:1;display:grid;grid-template-columns:320px 1fr;gap:2rem;padding:2rem} section{background:var(--panel);border-radius:var(--radius);padding:1.2rem;overflow:auto} label.file{display:block;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;border:none;background:transparent;color:inherit} #builder input[type=text]:focus{outline:1px solid var(--accent)} #builder button{background:var(--accent);border:none;color:#fff;padding:.3rem .6rem;border-radius:6px;cursor:pointer} #builder li.dragging{opacity:.4} button.big{padding:.8rem 1.6rem;border-radius:40px;background:var(--accent);border:none;color:#fff;font-weight:600;cursor:pointer;margin-top:1rem;width:100%} a{color:aqua;text-decoration:none;transition:.3s} a:hover{text-shadow:0 0 6px #0ff} @media (max-width: 800px) { main{grid-template-columns:1fr!important; /* stack panes */ padding:1rem!important; gap:1rem!important} section{max-width:100%!important} /* use full width*/ } </style></head><body> <h1>NavβMaker<span style="opacity:.6;font-size:.9rem"> Β· <?=htmlspecialchars($rootName)?><?= $menu?' / '.htmlspecialchars($menu):''?></span></h1> <main> <!-- LEFT PANE ---------------------------------------------------------> <section> <h2 style="margin-top:0;font-size:1.15rem">0 Β· Choose SiteβRoot</h2> <form><select name="root" style="width:100%;padding:.5rem;border-radius:8px;margin-bottom:1rem"> <?php foreach($allRoots as $r) echo "<option".($r===$rootName?' selected':'').'>'.htmlspecialchars($r).'</option>';?> </select><button class="big">Open Root</button></form> <?php if($rootName): ?> <hr style="border:none;border-top:1px solid #333;margin:1.3rem 0"> <h2 style="font-size:1.1rem">1 Β· Menu Name</h2> <form> <input type="hidden" name="root" value="<?=htmlspecialchars($rootName)?>"> <input name="menu" list="menuchoice" value="<?=htmlspecialchars($menu)?>" placeholder="primaryβ¦" style="width:100%;padding:.5rem;border-radius:8px;margin-bottom:1rem"> <datalist id="menuchoice"><?php foreach(existing_menus() as $m) echo "<option value=\"$m\">";?></datalist> <button class="big">Create / Load</button> </form> <br><br><a href="https://bestdealon.com/<?=htmlspecialchars($rootName)?>/site-builder.php" title="Builder">Site Builder</a> <?php endif; ?> <?php if($menu): ?> <hr style="border:none;border-top:1px solid #333;margin:1.3rem 0"> <h2 style="font-size:1.1rem">2 Β· Pick Pages</h2> <div style="max-height:45vh;overflow:auto"><?=listTree($rootPath)?></div> <?php endif;?> </section> <?php if($menu): ?> <!-- RIGHT PANE --------------------------------------------------------> <section> <h2 style="margin-top:0;font-size:1.2rem">3 Β· Arrange & Edit</h2> <label style="display:block;margin-bottom:.8rem"> Line colourΒ Β <input type="color" id="menucolor" value="<?=htmlspecialchars($color)?>"> </label> <ul id="builder"> <?php foreach($items as $i): ?> <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" <?=!empty($i['active'])?'checked':''?>>active</label> <button class="del">β</button> </li> <?php endforeach;?> </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"> <input type="text" class="path"> <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'); /* add from file tree */ document.querySelectorAll('label.file input').forEach(cb=>{ cb.addEventListener('change',()=>{ 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 row */ ul.addEventListener('click',e=>{ if(e.target.classList.contains('del')) e.target.closest('li').remove(); }); /* dragβsort */ let drag=null; ul.addEventListener('dragstart',e=>{drag=e.target;drag.classList.add('dragging')}); ul.addEventListener('dragend',()=>drag&&drag.classList.remove('dragging')); ul.addEventListener('dragover',e=>{ e.preventDefault(); const after=[...ul.querySelectorAll('li:not(.dragging)')].find(li=>e.clientY<li.getBoundingClientRect().top+li.offsetHeight/2); ul.insertBefore(drag,after); }); /* save */ document.getElementById('save').onclick=()=>{ const payload=[...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', root:'<?=htmlspecialchars($rootName)?>', menu:'<?=htmlspecialchars($menu)?>', color:document.getElementById('menucolor').value, json:JSON.stringify(payload) }) }).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