GitGram — pages.php — GitGram
IndexGram / main / indexgram_v5.00 / admin / pages.php26,078 B↓ Raw
<?php
require_once dirname(__DIR__) . '/config.php';
if (!IS_INSTALLED) { header('Location: ../setup.php'); exit; }
require_once ROOT_PATH . '/includes/db.php';
require_once ROOT_PATH . '/includes/functions.php';
require_once ROOT_PATH . '/includes/auth.php';
require_once ROOT_PATH . '/includes/markdown.php';
require_login(); require_role('editor');

// AJAX preview
if (isset($_GET['preview_md'])) {
    header('Content-Type: text/html; charset=UTF-8');
    echo md($_POST['content'] ?? '');
    exit;
}

$editSlug  = trim($_GET['edit'] ?? '');
$isNew     = ($editSlug === '__new__');
$page      = (!$isNew && $editSlug) ? get_page($editSlug) : null;

// Handle delete
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete') {
    verify_csrf();
    $delSlug = trim($_POST['slug'] ?? '');
    if ($delSlug && has_role('admin') && !in_array($delSlug, ['landing','bibliography'], true)) {
        db()->prepare("DELETE FROM pages WHERE slug=?")->execute([$delSlug]);
        flash('success', 'Page deleted.');
    }
    header('Location: pages.php'); exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') !== 'delete') {
    verify_csrf();
    $slug        = make_slug(trim($_POST['slug'] ?? ''));
    $title       = trim($_POST['title'] ?? '');
    $content     = $_POST['content'] ?? '';
    $enabled     = isset($_POST['enabled']) ? 1 : 0;
    $showInNav   = isset($_POST['show_in_nav']) ? 1 : 0;
    $navLabel    = trim($_POST['nav_label'] ?? '');
    $sortOrder   = (int)($_POST['sort_order'] ?? 0);
    $navTopicIds = implode(',', array_filter(array_map('intval', $_POST['nav_topic_ids'] ?? [])));
    $parentId    = (int)($_POST['parent_id'] ?? 0) ?: null;

    if (!$slug || !$title) {
        flash('error', 'Slug and title are required.');
    } else {
        $existing = get_page($slug);
        // Prevent a page from being its own parent
        if ($parentId && $existing && $existing['id'] === $parentId) {
            $parentId = null;
        }
        if ($existing) {
            db()->prepare(
                "UPDATE pages SET title=?,content=?,enabled=?,show_in_nav=?,nav_label=?,sort_order=?,nav_topic_ids=?,parent_id=?,updated_at=CURRENT_TIMESTAMP WHERE slug=?"
            )->execute([$title, $content, $enabled, $showInNav, $navLabel, $sortOrder, $navTopicIds, $parentId, $slug]);
        } else {
            db()->prepare(
                "INSERT INTO pages (slug,title,content,enabled,show_in_nav,nav_label,sort_order,nav_topic_ids,parent_id) VALUES (?,?,?,?,?,?,?,?,?)"
            )->execute([$slug, $title, $content, $enabled, $showInNav, $navLabel, $sortOrder, $navTopicIds, $parentId]);
        }
        flash('success', 'Page saved.');
        header('Location: pages.php?edit=' . rawurlencode($slug)); exit;
    }
    // Re-populate on error
    $page = compact('slug','title','content','enabled','showInNav','navLabel','sortOrder','navTopicIds','parentId');
    $page['show_in_nav']   = $showInNav;
    $page['nav_label']     = $navLabel;
    $page['sort_order']    = $sortOrder;
    $page['nav_topic_ids'] = $navTopicIds;
    $page['parent_id']     = $parentId;
}

$allPages  = get_all_pages();
$allTopics = get_topics(null, true);
$adminTitle = ($isNew || $page) ? ('Edit Page: ' . ($page ? h($page['title']) : 'New Page')) : 'Static Pages';
include ROOT_PATH . '/admin/header.php';
?>

<style>
.toolbar { display:flex; flex-wrap:wrap; gap:3px; margin-bottom:6px; }
.toolbar button { padding:3px 8px; font-size:12px; cursor:pointer;
                  background:#C0C0C0; border:1px solid #808080; }
.toolbar button:hover { background:#A0A0FF; color:#FFF; }
#page-content { font-family:'Courier New',monospace; font-size:13px; min-height:400px; resize:vertical; }
#preview-panel { background:#FFF; border:2px inset #808080; padding:10px;
                 min-height:400px; overflow-y:auto; font-size:14px; }
.tab-bar button { padding:4px 12px; font-size:13px; cursor:pointer; background:#A0A0A0;
                  color:#FFF; border:2px solid #808080; border-bottom:none; }
.tab-bar button.active { background:var(--titlebar-bg,#5B2D8E); color:#FFF; }
.media-grid { display:flex; flex-wrap:wrap; gap:6px; max-height:200px; overflow-y:auto;
              border:1px inset #808080; padding:6px; background:#FFF; }
.media-thumb { width:80px; height:60px; object-fit:cover; cursor:pointer; border:2px solid transparent; }
.media-thumb:hover { border-color:#5B2D8E; }
</style>

<?php if (!$page && !$isNew): ?>
<!-- Page list -->
<div style="margin-bottom:10px">
  <?php if (has_role('admin')): ?>
  <a href="pages.php?edit=__new__" class="btn btn-primary">+ New Custom Page</a>
  <?php endif; ?>
</div>
<div class="admin-card">
  <div class="admin-card-title">&#128196; Pages</div>
  <div class="admin-card-body">
    <table class="admin-table">
      <thead><tr><th>Slug</th><th>Title</th><th>Nav Label</th><th>Parent</th><th>Status</th><th>Nav</th><th></th></tr></thead>
      <tbody>
        <?php
        // Render top-level pages first, then their children indented below
        $topLevel = array_filter($allPages, fn($p) => empty($p['parent_id']));
        $childMap = [];
        foreach ($allPages as $p) {
            if (!empty($p['parent_id'])) $childMap[$p['parent_id']][] = $p;
        }
        $renderPageRow = function(array $p, bool $isChild) use ($childMap, &$renderPageRow): void {
            $isBuiltIn = in_array($p['slug'], ['landing','bibliography'], true);
            $parentPage = !empty($p['parent_id']) ? get_page_by_id((int)$p['parent_id']) : null;
            ?>
        <tr <?= $isChild ? 'style="background:rgba(0,0,0,0.03)"' : '' ?>>
          <td><?= $isChild ? '<span style="color:#888;margin-right:4px">↳</span>' : '' ?><code><?= h($p['slug']) ?></code></td>
          <td><?= h($p['title']) ?></td>
          <td><?= h($p['nav_label']) ?></td>
          <td style="font-size:11px"><?= $parentPage ? h($parentPage['title']) : '<span style="color:#888">—</span>' ?></td>
          <td><span class="badge <?= $p['enabled'] ? 'badge-pub' : 'badge-draft' ?>"><?= $p['enabled'] ? 'Enabled' : 'Disabled' ?></span></td>
          <td><?= $p['show_in_nav'] ? '&#10003;' : '&#10005;' ?></td>
          <td>
            <a href="pages.php?edit=<?= urlencode($p['slug']) ?>" class="btn btn-sm">&#9998; Edit</a>
            <?php if ($p['enabled'] && $p['slug'] !== '__new__'): ?>
              <a href="<?= h(base_url($p['slug'] === 'landing' ? '' : 'page/' . $p['slug'])) ?>"
                 target="_blank" class="btn btn-sm">&#128065; View</a>
            <?php endif; ?>
            <?php if (!$isBuiltIn && has_role('admin')): ?>
            <form method="post" style="display:inline" onsubmit="return confirm('Delete this page?')">
              <input type="hidden" name="csrf_token" value="<?= csrf_token() ?>">
              <input type="hidden" name="action" value="delete">
              <input type="hidden" name="slug" value="<?= h($p['slug']) ?>">
              <button type="submit" class="btn btn-danger btn-sm">Del</button>
            </form>
            <?php endif; ?>
          </td>
        </tr>
            <?php
            // Render children of this page immediately after
            if (!empty($childMap[$p['id']])) {
                foreach ($childMap[$p['id']] as $child) {
                    $renderPageRow($child, true);
                }
            }
        };
        foreach ($topLevel as $p) $renderPageRow($p, false);
        ?>
      </tbody>
    </table>
    <p style="font-size:12px;color:#555;margin-top:8px">
      The <strong>Landing</strong> page content appears above the topic grid on the homepage when enabled.<br>
      Custom pages are accessible at <code>/page/your-slug</code> and can appear in the nav. Sub-pages appear as nav dropdown items under their parent.
    </p>
  </div>
</div>

<?php else: ?>
<!-- Edit form (new or existing page) -->
<form method="post" id="page-form">
<input type="hidden" name="csrf_token" value="<?= csrf_token() ?>">
<?php if ($isNew): ?>
<div class="form-row" style="margin-bottom:10px">
  <label style="font-weight:bold;font-size:12px">Page Slug <small>(URL-safe, e.g. <code>about-us</code>)</small></label>
  <input type="text" name="slug" class="input-full" required placeholder="e.g. about-us"
         id="new-page-slug" value="<?= h($page['slug'] ?? '') ?>">
</div>
<?php else: ?>
<input type="hidden" name="slug" value="<?= h($page['slug']) ?>">
<?php endif; ?>

<div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:10px;align-items:flex-end">
  <div style="flex:3;min-width:200px">
    <label style="font-weight:bold;font-size:12px">Page Title</label>
    <input type="text" name="title" id="page-title-input" class="input-full" required
           value="<?= h($page['title'] ?? '') ?>">
  </div>
  <div style="flex:2;min-width:150px">
    <label style="font-weight:bold;font-size:12px">Nav Label <small>(shown in navigation)</small></label>
    <input type="text" name="nav_label" class="input-full" value="<?= h($page['nav_label'] ?? ($page['title'] ?? '')) ?>">
  </div>
  <div style="min-width:80px">
    <label style="font-weight:bold;font-size:12px">Order</label>
    <input type="number" name="sort_order" class="input-full" min="0" value="<?= (int)($page['sort_order'] ?? 0) ?>">
  </div>
</div>

<?php
  $isBuiltInPage = !$isNew && in_array($page['slug'] ?? '', ['landing','bibliography'], true);
  $currentParentId = (int)($page['parent_id'] ?? 0);
  // Pages eligible as parents: all pages except self, excluding landing from being a child
  $eligibleParents = array_filter($allPages, fn($p) =>
      $p['slug'] !== ($page['slug'] ?? '__self__')
  );
?>
<?php if (!$isBuiltInPage): ?>
<div class="form-row" style="margin-bottom:10px">
  <label style="font-weight:bold;font-size:12px">Parent Page <small>(optional — makes this a sub-page)</small></label>
  <select name="parent_id" style="font-size:13px;padding:4px 6px;border:2px solid;border-color:#808080 #FFF #FFF #808080;max-width:360px">
    <option value="0">— None (top-level page) —</option>
    <?php foreach ($eligibleParents as $ep): ?>
      <option value="<?= (int)$ep['id'] ?>" <?= $currentParentId === (int)$ep['id'] ? 'selected' : '' ?>>
        <?= h($ep['title']) ?><?= $ep['nav_label'] && $ep['nav_label'] !== $ep['title'] ? ' (' . h($ep['nav_label']) . ')' : '' ?>
      </option>
    <?php endforeach; ?>
  </select>
  <p style="font-size:11px;color:#555;margin:3px 0 0">Sub-pages appear as dropdown items under the parent in the nav, and are listed at the bottom of the parent page.</p>
</div>
<?php endif; ?>

<div style="display:flex;gap:16px;flex-wrap:wrap;margin-bottom:10px">
  <label style="font-size:13px;cursor:pointer">
    <input type="checkbox" name="enabled" value="1" <?= !empty($page['enabled']) ? 'checked' : '' ?>>
    Enabled (publicly visible)
  </label>
  <label style="font-size:13px;cursor:pointer">
    <input type="checkbox" name="show_in_nav" value="1" <?= !isset($page['show_in_nav']) || $page['show_in_nav'] ? 'checked' : '' ?>>
    Show in navigation
  </label>
</div>

<?php if (!empty($allTopics)): ?>
<div class="admin-card" style="margin-bottom:10px">
  <div class="admin-card-title">&#128193; Navigation Sub-menu Topics <small style="font-weight:normal">(shown as dropdown under this page in the nav)</small></div>
  <div class="admin-card-body">
    <?php
      $navTopicRaw = $page['nav_topic_ids'] ?? '';
      $currentNavTopicIds = !empty($navTopicRaw)
        ? array_filter(array_map('intval', explode(',', $navTopicRaw)))
        : [];
    ?>
    <div style="display:flex;flex-wrap:wrap;gap:4px">
      <?php foreach ($allTopics as $t):
        $depth = $t['parent_id'] ? (get_topic_by_id((int)$t['parent_id'])['parent_id'] ? '&nbsp;&nbsp;&nbsp;&nbsp;» ' : '&nbsp;&nbsp;› ') : '';
      ?>
      <label style="font-size:12px;cursor:pointer;display:flex;align-items:center;gap:3px;padding:3px 8px;background:#EDE8F5;border:1px solid #9888B0">
        <input type="checkbox" name="nav_topic_ids[]" value="<?= $t['id'] ?>"
               <?= in_array((int)$t['id'], $currentNavTopicIds, true) ? 'checked' : '' ?>>
        <?= $depth . h($t['title']) ?>
      </label>
      <?php endforeach; ?>
    </div>
    <p style="font-size:11px;color:#666;margin:6px 0 0">If any are selected, the nav item for this page becomes a dropdown. Topics are shown in their current sort order.</p>
  </div>
</div>
<?php endif; ?>

<div class="admin-card" style="margin-bottom:10px">
  <div class="admin-card-title">Content <small style="font-weight:normal">(Markdown)</small></div>
  <div class="admin-card-body">
    <div class="toolbar" id="md-toolbar">
      <button type="button" data-wrap="**"  data-wrap-end="**"  title="Bold"><strong>B</strong></button>
      <button type="button" data-wrap="*"   data-wrap-end="*"   title="Italic"><em>I</em></button>
      <button type="button" data-wrap="~~"  data-wrap-end="~~"  title="Strikethrough"><del>S</del></button>
      <button type="button" data-prefix="# "   title="H1">H1</button>
      <button type="button" data-prefix="## "  title="H2">H2</button>
      <button type="button" data-prefix="### " title="H3">H3</button>
      <button type="button" data-prefix="> "   title="Blockquote">&#8220;&#8221;</button>
      <button type="button" data-prefix="- "   title="Bullet list">&#8226; List</button>
      <button type="button" data-prefix="1. "  title="Numbered list"># List</button>
      <button type="button" data-wrap="`"   data-wrap-end="`"  title="Code span">&#60;/&#62;</button>
      <button type="button" data-block="```\n" data-block-end="\n```" title="Code block">Block</button>
      <button type="button" id="btn-link"  title="Link">&#128279; Link</button>
      <button type="button" id="btn-img"   title="Image">&#128247; Image</button>
      <button type="button" id="btn-video" title="Video">&#127909; Video</button>
      <button type="button" id="btn-audio" title="Audio">&#127925; Audio</button>
      <button type="button" id="btn-pdf"   title="PDF embed">&#128196; PDF</button>
      <button type="button" id="btn-table" title="Table">&#128202; Table</button>
      <button type="button" data-insert="\n---\n" title="HR">&#8213; HR</button>
      <span style="margin-left:8px">
        <button type="button" id="btn-preview">Preview</button>
        <button type="button" id="btn-edit-mode" class="active">Write</button>
      </span>
    </div>
    <div id="editor-area">
      <textarea name="content" id="page-content" class="input-full"><?= h($page['content'] ?? '') ?></textarea>
    </div>
    <div id="preview-panel" style="display:none"><em style="color:#888">Loading…</em></div>

    <!-- Media upload & insert -->
    <div style="margin-top:8px">
      <strong style="font-size:12px">Upload &amp; Insert Media</strong>
      <div style="display:flex;gap:8px;margin:6px 0;flex-wrap:wrap;align-items:center">
        <input type="file" id="media-file-input" accept="image/*,audio/*,application/pdf,.pdf" style="font-size:12px">
        <button type="button" id="media-upload-btn" class="btn btn-sm">Upload &amp; Insert</button>
        <span id="media-upload-status" style="font-size:12px;color:#555"></span>
      </div>
      <strong style="font-size:12px">Existing Media (click to insert):</strong>
      <div class="media-grid" id="media-grid">
        <?php
        $mediaFiles = db()->query("SELECT * FROM media ORDER BY created_at DESC LIMIT 80")->fetchAll();
        foreach ($mediaFiles as $mf):
          $isImg = ($mf['file_type'] === 'image');
          $isPdf = ($mf['file_type'] === 'pdf');
          $subdir = $isImg ? 'images' : ($isPdf ? 'pdf' : 'audio');
          $murl   = base_url('uploads/' . $subdir . '/' . $mf['filename']);
        ?>
        <?php if ($isImg): ?>
          <img src="<?= h($murl) ?>" alt="<?= h($mf['original_name']) ?>" class="media-thumb"
               data-url="<?= h($murl) ?>" data-type="image" data-name="<?= h($mf['original_name']) ?>"
               title="<?= h($mf['original_name']) ?>">
        <?php elseif ($isPdf): ?>
          <span style="cursor:pointer;padding:4px;border:2px solid #5B2D8E;font-size:11px;background:#E8E8FF;display:inline-block;max-width:100px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis"
                data-url="<?= h($murl) ?>" data-type="pdf" data-name="<?= h($mf['original_name']) ?>"
                class="media-pdf-item" title="Click to embed PDF: <?= h($mf['original_name']) ?>">
            &#128196; <?= h(substr($mf['original_name'], 0, 16)) ?>
          </span>
        <?php else: ?>
          <span style="cursor:pointer;padding:4px;border:1px solid #808080;font-size:11px;background:#EEE"
                data-url="<?= h($murl) ?>" data-type="audio" data-name="<?= h($mf['original_name']) ?>"
                class="media-audio-item" title="Click to insert audio">
            &#127925; <?= h(substr($mf['original_name'], 0, 14)) ?>
          </span>
        <?php endif; ?>
        <?php endforeach; ?>
      </div>
    </div>
  </div>
</div>

<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
  <button type="submit" class="btn btn-primary">&#10003; Save Page</button>
  <a href="pages.php" class="btn">&#8592; All Pages</a>
  <?php if (!$isNew && !empty($page['enabled']) && !empty($page['slug'])): ?>
    <a href="<?= h(base_url($page['slug'] === 'landing' ? '' : 'page/' . $page['slug'])) ?>"
       target="_blank" class="btn">&#128065; View Live</a>
  <?php endif; ?>
</div>
</form>

<script>
(function(){
  var textarea     = document.getElementById('page-content');
  var previewPanel = document.getElementById('preview-panel');
  var editorArea   = document.getElementById('editor-area');
  var btnPreview   = document.getElementById('btn-preview');
  var btnEdit      = document.getElementById('btn-edit-mode');

  // Preview / Write toggle
  btnPreview.addEventListener('click', function(){
    var fd = new FormData();
    fd.append('content', textarea.value);
    fetch('pages.php?preview_md=1', {method:'POST', body:fd})
      .then(function(r){ return r.text(); })
      .then(function(html){
        previewPanel.innerHTML = html;
        editorArea.style.display   = 'none';
        previewPanel.style.display = 'block';
        btnPreview.classList.add('active');
        btnEdit.classList.remove('active');
      });
  });
  btnEdit.addEventListener('click', function(){
    editorArea.style.display   = '';
    previewPanel.style.display = 'none';
    btnEdit.classList.add('active');
    btnPreview.classList.remove('active');
    textarea.focus();
  });

  // Toolbar — data-attribute buttons
  document.getElementById('md-toolbar').addEventListener('click', function(e){
    var btn = e.target.closest('button[data-wrap],button[data-prefix],button[data-insert],button[data-block]');
    if (!btn) return;
    textarea.focus();
    var start = textarea.selectionStart, end = textarea.selectionEnd;
    var sel   = textarea.value.substring(start, end);
    var text  = textarea.value;
    var ins;
    if (btn.dataset.wrap) {
      var w = btn.dataset.wrap, we = btn.dataset.wrapEnd || w;
      ins = w + (sel || 'text') + we;
    } else if (btn.dataset.prefix) {
      ins = '\n' + btn.dataset.prefix + (sel || 'text');
    } else if (btn.dataset.insert) {
      ins = btn.dataset.insert.replace(/\\n/g, '\n');
    } else if (btn.dataset.block) {
      var bs = btn.dataset.block.replace(/\\n/g,'\n'), be = (btn.dataset.blockEnd||'').replace(/\\n/g,'\n');
      ins = bs + (sel || 'code here') + be;
    } else { return; }
    textarea.value = text.substring(0,start) + ins + text.substring(end);
    textarea.selectionStart = textarea.selectionEnd = start + ins.length;
  });

  // Special toolbar buttons
  document.getElementById('btn-link').addEventListener('click', function(){
    var url = prompt('Enter URL:'); if (!url) return;
    var lbl = prompt('Link text:', 'link text') || url;
    insertAt('[' + lbl + '](' + url + ')');
  });
  document.getElementById('btn-img').addEventListener('click', function(){
    var url = prompt('Image URL or filename (from uploads):'); if (!url) return;
    var alt = prompt('Alt text:', 'image') || 'image';
    insertAt('![' + alt + '](' + url + ')');
  });
  document.getElementById('btn-video').addEventListener('click', function(){
    var url = prompt('Paste video URL (YouTube, Vimeo, Rumble, Odysee, BitChute, PeerTube, TikTok):\n\nOr paste an embed <iframe> code:');
    if (!url) return;
    if (url.trim().startsWith('<')) {
      insertAt('\n' + url.trim() + '\n');
    } else {
      insertAt('\n[video](' + url.trim() + ')\n');
    }
  });
  document.getElementById('btn-audio').addEventListener('click', function(){
    var url = prompt('Audio file URL or filename:'); if (!url) return;
    insertAt('\n[audio](' + url + ')\n');
  });
  document.getElementById('btn-pdf').addEventListener('click', function(){
    var url = prompt('PDF URL (https://…) or uploaded filename:\n\nTip: Upload the PDF first using the media section below, then click it in the grid to insert automatically.');
    if (!url) return;
    url = url.trim();
    if (!/^https?:\/\//.test(url) && !/^\//.test(url) && url.indexOf('/') === -1) {
      url = '<?= h(base_url('uploads/pdf/')) ?>' + url;
    }
    insertAt('\n[pdf](' + url + ')\n');
  });
  document.getElementById('btn-table').addEventListener('click', function(){
    var cols = parseInt(prompt('Columns:', '3'), 10) || 3;
    var rows = parseInt(prompt('Data rows:', '2'), 10) || 2;
    var hdr = '| ' + Array.from({length:cols}, function(_,i){ return 'Col '+(i+1); }).join(' | ') + ' |';
    var sep = '| ' + Array.from({length:cols}, function(){ return '---'; }).join(' | ') + ' |';
    var row = '| ' + Array.from({length:cols}, function(){ return 'Cell'; }).join(' | ') + ' |';
    insertAt('\n' + hdr + '\n' + sep + '\n' + Array.from({length:rows},function(){return row;}).join('\n') + '\n');
  });

  function insertAt(text) {
    textarea.focus();
    var s = textarea.selectionStart;
    textarea.value = textarea.value.substring(0,s) + text + textarea.value.substring(textarea.selectionEnd);
    textarea.selectionStart = textarea.selectionEnd = s + text.length;
  }

  // Media upload & insert
  document.getElementById('media-upload-btn').addEventListener('click', function(){
    var fileInput = document.getElementById('media-file-input');
    if (!fileInput.files.length) { alert('Select a file first.'); return; }
    var fd = new FormData();
    fd.append('file', fileInput.files[0]);
    fd.append('csrf_token', '<?= csrf_token() ?>');
    var status = document.getElementById('media-upload-status');
    status.textContent = 'Uploading…';
    fetch('<?= h(base_url('admin/upload.php')) ?>', {method:'POST', body:fd})
      .then(function(r){ return r.json(); })
      .then(function(data){
        if (data.success) {
          status.textContent = 'Uploaded!';
          var url  = data.url;
          var name = data.original_name;
          if (data.file_type === 'image') {
            insertAt('\n![' + name + '](' + url + ')\n');
          } else if (data.file_type === 'pdf') {
            insertAt('\n[pdf](' + url + ')\n');
          } else {
            insertAt('\n[audio](' + url + ')\n');
          }
          // Prepend to grid
          var grid = document.getElementById('media-grid');
          var el;
          if (data.file_type === 'image') {
            el = document.createElement('img');
            el.src = url; el.className = 'media-thumb';
            el.dataset.url = url; el.dataset.type = 'image'; el.dataset.name = name;
            el.title = name;
          } else if (data.file_type === 'pdf') {
            el = document.createElement('span');
            el.textContent = '\u{1F4C4} ' + name.substring(0,16);
            el.className = 'media-pdf-item';
            el.dataset.url = url; el.dataset.type = 'pdf'; el.dataset.name = name;
            el.title = 'Click to embed PDF: ' + name;
            el.style.cssText = 'cursor:pointer;padding:4px;border:2px solid #5B2D8E;font-size:11px;background:#E8E8FF;display:inline-block;max-width:100px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis';
          } else {
            el = document.createElement('span');
            el.textContent = '\u{1F3B5} ' + name.substring(0,14);
            el.className = 'media-audio-item';
            el.dataset.url = url; el.dataset.type = 'audio'; el.dataset.name = name;
            el.style.cssText = 'cursor:pointer;padding:4px;border:1px solid #808080;font-size:11px;background:#EEE';
          }
          grid.insertBefore(el, grid.firstChild);
          fileInput.value = '';
          setTimeout(function(){ status.textContent = ''; }, 3000);
        } else {
          status.textContent = 'Error: ' + (data.error || 'Unknown error');
        }
      })
      .catch(function(){ status.textContent = 'Upload failed.'; });
  });

  // Click media grid item to insert
  document.getElementById('media-grid').addEventListener('click', function(e){
    var el = e.target.closest('[data-url]');
    if (!el) return;
    var url  = el.dataset.url;
    var name = el.dataset.name || 'file';
    var type = el.dataset.type || 'image';
    if (type === 'image') {
      insertAt('\n![' + name + '](' + url + ')\n');
    } else if (type === 'pdf') {
      insertAt('\n[pdf](' + url + ')\n');
    } else {
      insertAt('\n[audio](' + url + ')\n');
    }
  });

  // Auto-generate slug from title for new pages
  var titleInp = document.getElementById('page-title-input');
  var slugInp  = document.getElementById('new-page-slug');
  if (titleInp && slugInp) {
    titleInp.addEventListener('input', function(){
      if (!slugInp.dataset.manual) {
        slugInp.value = titleInp.value.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');
      }
    });
    slugInp.addEventListener('input', function(){ slugInp.dataset.manual = '1'; });
  }

})();
</script>
<?php endif; ?>

<?php include ROOT_PATH . '/admin/footer.php'; ?>
Ready
GitGram