readysite / website / frontend / pages / PageEditor.jsx
42.5 KB
PageEditor.jsx
import * as React from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { apiGet, apiPost, apiPut, apiDelete } from '../hooks/useAPI.js';
import { useChat } from '../layouts/AdminLayout.jsx';
import { MonacoEditor } from '../components/MonacoEditor.jsx';

export function PageEditor() {
  const { toggleChat } = useChat() || {};
  const { id } = useParams();
  const navigate = useNavigate();
  const [page, setPage] = useState(null);
  const [pages, setPages] = useState([]);
  const [versions, setVersions] = useState([]);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [tab, setTab] = useState('preview');
  const [showEditModal, setShowEditModal] = useState(false);
  const [showVersionDialog, setShowVersionDialog] = useState(false);
  const [versionDialogTitle, setVersionDialogTitle] = useState('');
  const [versionDialogContent, setVersionDialogContent] = useState(null);
  const [codeValue, setCodeValue] = useState('');
  const [originalCode, setOriginalCode] = useState('');
  const [codeDirty, setCodeDirty] = useState(false);
  const [showTemplateHelp, setShowTemplateHelp] = useState(false);
  const iframeRef = useRef(null);

  const fetchPage = useCallback(() => {
    if (!id) return;
    setLoading(true);
    Promise.all([
      apiGet(`/api/admin/pages/${id}`),
      apiGet('/api/admin/pages'),
      apiGet(`/api/admin/pages/${id}/versions`).catch(() => ({ items: [] })),
    ]).then(([pageData, pagesData, versionsData]) => {
      setPage(pageData);
      setPages(pagesData?.items || []);
      setVersions(versionsData?.items || []);
      setCodeValue(pageData?.html || '');
      setOriginalCode(pageData?.html || '');
      setCodeDirty(false);
    }).finally(() => setLoading(false));
  }, [id]);

  useEffect(() => {
    fetchPage();
  }, [fetchPage]);

  useEffect(() => {
    setCodeDirty(codeValue !== originalCode);
  }, [codeValue, originalCode]);

  const togglePublish = async () => {
    if (!id) return;
    setSaving(true);
    try {
      await apiPost(`/api/admin/pages/${id}/publish`);
      fetchPage();
    } finally {
      setSaving(false);
    }
  };

  const saveCode = async () => {
    if (!id || !codeDirty) return;
    setSaving(true);
    try {
      await apiPost(`/api/admin/pages/${id}/code`, { html: codeValue });
      setOriginalCode(codeValue);
      setCodeDirty(false);
      fetchPage();
    } finally {
      setSaving(false);
    }
  };

  const handleEditSubmit = async (e) => {
    e.preventDefault();
    if (!id) return;
    const form = e.target;
    const formData = new FormData(form);

    setSaving(true);
    try {
      await apiPut(`/api/admin/pages/${id}`, {
        title: formData.get('title'),
        description: formData.get('description'),
        parentId: formData.get('parent_id') || '',
        published: formData.get('published') === 'on',
      });
      setShowEditModal(false);
      fetchPage();
    } finally {
      setSaving(false);
    }
  };

  const deletePage = async () => {
    if (!id || !confirm('Are you sure you want to delete this page?')) return;
    await apiDelete(`/api/admin/pages/${id}`);
    navigate('/pages');
  };

  const showVersionPreview = async (contentId, title) => {
    setVersionDialogTitle(`Preview: ${title}`);
    setVersionDialogContent(<div className="loading loading-spinner loading-lg mx-auto block" />);
    setShowVersionDialog(true);

    try {
      const data = await apiGet(`/api/admin/pages/${id}/content/${contentId}`);
      setVersionDialogContent(
        <div className="prose prose-sm max-w-none bg-base-200 p-4 rounded-lg overflow-auto">
          <pre className="whitespace-pre-wrap text-xs"><code>{data.html || 'No content'}</code></pre>
        </div>
      );
    } catch (err) {
      setVersionDialogContent(<div className="text-error">Failed to load version: {err.message}</div>);
    }
  };

  const showVersionDiff = async (contentId, title) => {
    setVersionDialogTitle(`Changes in: ${title}`);
    setVersionDialogContent(<div className="loading loading-spinner loading-lg mx-auto block" />);
    setShowVersionDialog(true);

    try {
      const [current, old] = await Promise.all([
        apiGet(`/api/admin/pages/${id}/content/latest`),
        apiGet(`/api/admin/pages/${id}/content/${contentId}`),
      ]);

      const currentHtml = current.html || '';
      const oldHtml = old.html || '';

      // Simple line-by-line diff
      const currentLines = currentHtml.split('\n');
      const oldLines = oldHtml.split('\n');

      setVersionDialogContent(
        <div>
          <div className="font-mono text-xs space-y-0 bg-base-200 rounded-lg p-2 overflow-auto max-h-96">
            {oldLines.map((line, i) => {
              const inCurrent = currentLines.includes(line);
              if (!inCurrent) {
                return (
                  <div key={`old-${i}`} className="bg-error/20 text-error line-through px-2 py-0.5">
                    <span className="select-none mr-2">-</span>{line}
                  </div>
                );
              }
              return null;
            })}
            {currentLines.map((line, i) => {
              const inOld = oldLines.includes(line);
              return (
                <div key={`cur-${i}`} className={`px-2 py-0.5 ${!inOld ? 'bg-success/20 text-success' : 'text-base-content/60'}`}>
                  <span className="select-none mr-2">{!inOld ? '+' : ' '}</span>{line}
                </div>
              );
            })}
          </div>
          <p className="text-xs text-base-content/50 mt-2">Showing changes from selected version to current version. Green = added, Red = removed.</p>
        </div>
      );
    } catch (err) {
      setVersionDialogContent(<div className="text-error">Failed to load diff: {err.message}</div>);
    }
  };

  const restoreVersion = async (contentId) => {
    if (!confirm('Restore this version?')) return;
    await apiPost(`/api/admin/pages/${id}/restore/${contentId}`);
    fetchPage();
  };

  const handleIframeLoad = async () => {
    try {
      const iframe = iframeRef.current;
      if (!iframe) return;
      const iframePath = iframe.contentWindow?.location?.pathname;
      if (!iframePath) return;

      // Handle /preview/{id} URLs
      if (iframePath.startsWith('/preview/')) {
        const newPageId = iframePath.replace('/preview/', '').split('/')[0];
        if (newPageId && newPageId !== id) {
          navigate(`/pages/${newPageId}`);
        }
        return;
      }

      // Handle regular page URLs - find page by path
      // First check our loaded pages
      const matchedPage = pages.find(p => p.path === iframePath);
      if (matchedPage && matchedPage.id !== id) {
        navigate(`/pages/${matchedPage.id}`);
        return;
      }

      // Fallback: path "/" = home, "/about" = about, etc.
      const guessedId = iframePath === '/' ? 'home' : iframePath.replace(/^\//, '').replace(/\/$/, '');
      if (guessedId && guessedId !== id && !guessedId.includes('/')) {
        // Verify page exists before navigating
        try {
          await apiGet(`/api/admin/pages/${guessedId}`);
          navigate(`/pages/${guessedId}`);
        } catch {
          // Page doesn't exist with this ID, ignore
        }
      }
    } catch {
      // Cross-origin access may fail, ignore
    }
  };

  if (loading || !page) {
    return (
      <div className="flex flex-col h-full">
        <div className="flex items-center justify-between px-4 py-3 border-b border-base-300 bg-base-200/50">
          <div className="skeleton h-6 w-48" />
        </div>
        <div className="flex-1 flex justify-center items-center">
          <span className="loading loading-spinner loading-lg" />
        </div>
      </div>
    );
  }

  return (
    <div className="flex flex-col h-full">
      {/* Header */}
      <div className="flex items-center justify-between px-4 py-3 border-b border-base-300 bg-base-200/50">
        <div className="flex items-center gap-3">
          <h2 className="font-semibold">{page.title}</h2>
          <button
            onClick={togglePublish}
            disabled={saving}
            className={`badge badge-soft ${page.published ? 'badge-success' : 'badge-warning'} badge-sm cursor-pointer hover:opacity-80 transition-opacity`}
          >
            {page.published ? 'Published' : 'Draft'}
          </button>
        </div>
        <div className="flex items-center gap-2">
          {/* Tabs */}
          <div className="tabs tabs-bordered">
            <a className={`tab ${tab === 'preview' ? 'tab-active' : ''}`} onClick={() => setTab('preview')}>Preview</a>
            <a className={`tab ${tab === 'code' ? 'tab-active' : ''}`} onClick={() => setTab('code')}>Code</a>
          </div>
          <button onClick={() => setShowEditModal(true)} className="btn btn-sm btn-ghost gap-1">
            <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
            </svg>
            Edit
          </button>
          {toggleChat && (
            <button onClick={toggleChat} className="btn btn-soft btn-primary btn-sm gap-2">
              <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
              </svg>
              AI Assistant
            </button>
          )}
        </div>
      </div>

      {/* Preview Content */}
      {tab === 'preview' && (
        <div className="flex-1 overflow-hidden">
          <iframe
            ref={iframeRef}
            src={`/preview/${id}`}
            className="w-full h-full border-0 bg-white"
            onLoad={handleIframeLoad}
          />
        </div>
      )}

      {/* Code Content */}
      {tab === 'code' && (
        <div className="flex-1 overflow-hidden flex flex-col">
          {/* Code Editor Toolbar */}
          <div className="flex items-center justify-between px-4 py-2 bg-base-300/50 border-b border-base-300">
            <div className="flex items-center gap-2">
              <span className="text-xs text-base-content/50">Go HTML Template</span>
              {codeDirty && <span className="badge badge-warning badge-xs">Unsaved</span>}
            </div>
            <div className="flex items-center gap-2">
              <button
                onClick={() => setShowTemplateHelp(true)}
                className="btn btn-ghost btn-xs gap-1"
                title="Template Reference"
              >
                <svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
                Help
              </button>
              {codeDirty && (
                <button onClick={saveCode} disabled={saving} className="btn btn-primary btn-sm gap-1">
                  {saving ? (
                    <span className="loading loading-spinner loading-sm" />
                  ) : (
                    <>
                      <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
                      </svg>
                      Save
                    </>
                  )}
                </button>
              )}
            </div>
          </div>
          {/* Code Editor */}
          <div className="flex-1 overflow-hidden relative">
            <MonacoEditor
              value={codeValue}
              onChange={setCodeValue}
              onSave={saveCode}
              language="gohtml"
              theme="gohtml-dark"
            />
          </div>
        </div>
      )}

      {/* Version History */}
      {versions.length > 1 && (
        <div className="w-full border-t border-base-300 bg-base-200/50">
          <details className="collapse collapse-arrow">
            <summary className="collapse-title text-sm font-medium py-2 min-h-0">
              Version History ({versions.length})
            </summary>
            <div className="collapse-content px-0 w-full">
              <ul className="menu menu-sm p-0 max-h-48 overflow-y-auto w-full">
                {versions.map((v, i) => (
                  <li key={v.id}>
                    <div className="flex justify-between items-center">
                      <div className="flex flex-col">
                        <span className="text-xs font-medium">
                          {v.title}
                          {i === 0 && <span className="badge badge-success badge-xs ml-1">Current</span>}
                        </span>
                        <span className="text-xs text-base-content/50">
                          {new Date(v.created).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}
                          {v.creatorName && ` · ${v.creatorName}`}
                        </span>
                      </div>
                      {i !== 0 && (
                        <div className="flex gap-1">
                          <button onClick={() => showVersionPreview(v.id, v.title)} className="btn btn-ghost btn-xs">Preview</button>
                          <button onClick={() => showVersionDiff(v.id, v.title)} className="btn btn-ghost btn-xs">Diff</button>
                          <button onClick={() => restoreVersion(v.id)} className="btn btn-ghost btn-xs text-warning">Restore</button>
                        </div>
                      )}
                    </div>
                  </li>
                ))}
              </ul>
            </div>
          </details>
        </div>
      )}

      {/* Edit Page Modal */}
      {showEditModal && (
        <dialog className="modal modal-open">
          <div className="modal-box w-11/12 max-w-2xl">
            <button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setShowEditModal(false)}>✕</button>
            <h3 className="font-bold text-lg mb-4">Edit Page</h3>

            <form onSubmit={handleEditSubmit} className="space-y-4">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                <div className="form-control">
                  <label className="floating-label">
                    <span>Title</span>
                    <input type="text" name="title" placeholder="Page Title" className="input input-bordered w-full" defaultValue={page.title} required autoFocus />
                  </label>
                </div>

                <div className="form-control">
                  <label className="floating-label">
                    <span>Slug</span>
                    <input type="text" name="slug" placeholder="page-slug" className="input input-bordered w-full bg-base-200" defaultValue={page.id} readOnly />
                  </label>
                  <label className="label">
                    <span className="label-text-alt text-base-content/50">Slug cannot be changed</span>
                  </label>
                </div>

                <div className="form-control md:col-span-2">
                  <label className="floating-label">
                    <span>Meta Description</span>
                    <input type="text" name="description" placeholder="Brief description for search engines" className="input input-bordered w-full" defaultValue={page.description || ''} />
                  </label>
                </div>

                <div className="form-control">
                  <label className="label">
                    <span className="label-text text-xs">Parent Page</span>
                  </label>
                  <select name="parent_id" className="select select-bordered select-sm w-full" defaultValue={page.parentId || ''}>
                    <option value="">No Parent (Root)</option>
                    {pages.filter(p => p.id !== page.id).map((p) => (
                      <option key={p.id} value={p.id}>{p.title}</option>
                    ))}
                  </select>
                </div>

                <div className="form-control flex-row items-center gap-3 pt-5">
                  <input type="checkbox" name="published" className="checkbox checkbox-primary checkbox-sm" defaultChecked={page.published} />
                  <span className="text-sm">Published</span>
                </div>
              </div>

              <div className="flex justify-end gap-2 pt-4">
                <button type="button" onClick={deletePage} className="btn btn-ghost btn-sm text-error mr-auto">Delete</button>
                <button type="button" className="btn btn-ghost btn-sm" onClick={() => setShowEditModal(false)}>Cancel</button>
                <button type="submit" className="btn btn-primary btn-sm" disabled={saving}>
                  {saving ? <span className="loading loading-spinner loading-sm" /> : 'Save Changes'}
                </button>
              </div>
            </form>
          </div>
          <div className="modal-backdrop" onClick={() => setShowEditModal(false)} />
        </dialog>
      )}

      {/* Version Preview/Diff Dialog */}
      {showVersionDialog && (
        <dialog className="modal modal-open">
          <div className="modal-box w-11/12 max-w-4xl max-h-[85vh]">
            <button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setShowVersionDialog(false)}>✕</button>
            <h3 className="font-bold text-lg mb-4">{versionDialogTitle}</h3>
            <div className="overflow-auto max-h-[65vh]">
              {versionDialogContent}
            </div>
            <div className="flex justify-end gap-2 mt-4 pt-4 border-t border-base-300">
              <button type="button" className="btn btn-ghost btn-sm" onClick={() => setShowVersionDialog(false)}>Close</button>
            </div>
          </div>
          <div className="modal-backdrop" onClick={() => setShowVersionDialog(false)} />
        </dialog>
      )}

      {/* Template Help Modal */}
      {showTemplateHelp && (
        <dialog className="modal modal-open">
          <div className="modal-box w-11/12 max-w-4xl max-h-[85vh]">
            <button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setShowTemplateHelp(false)}>✕</button>
            <h3 className="font-bold text-lg mb-4">Template Reference</h3>
            <div className="overflow-auto max-h-[65vh] space-y-6">

              {/* Syntax highlighting color classes */}
              <style>{`
                .hl-delim { color: #C586C0; font-weight: bold; }
                .hl-keyword { color: #C586C0; }
                .hl-func { color: #4EC9B0; }
                .hl-builtin { color: #569CD6; }
                .hl-var { color: #9CDCFE; }
                .hl-string { color: #CE9178; }
                .hl-number { color: #B5CEA8; }
                .hl-tag { color: #569CD6; }
                .hl-attr { color: #9CDCFE; }
                .hl-code { background: #1e1e1e; padding: 0.75rem; border-radius: 0.5rem; }
              `}</style>

              {/* Basic Syntax */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Basic Syntax</h4>
                <div className="hl-code font-mono text-xs space-y-1">
                  <div><span className="hl-delim">{'{{'}</span> expression <span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Output a value</span></div>
                  <div><span className="hl-delim">{'{{-'}</span> expression <span className="hl-delim">{'-}}'}</span> <span className="text-base-content/50">- Trim whitespace</span></div>
                </div>
              </div>

              {/* Control Flow */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Control Flow</h4>
                <div className="hl-code font-mono text-xs space-y-2">
                  <div className="border-b border-base-content/10 pb-2">
                    <div className="text-base-content/50 mb-1">Conditionals:</div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-keyword">if</span> <span className="hl-var">.Published</span><span className="hl-delim">{'}}'}</span>...<span className="hl-delim">{'{{'}</span><span className="hl-keyword">else</span><span className="hl-delim">{'}}'}</span>...<span className="hl-delim">{'{{'}</span><span className="hl-keyword">end</span><span className="hl-delim">{'}}'}</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-keyword">if</span> <span className="hl-builtin">eq</span> <span className="hl-var">.Status</span> <span className="hl-string">"active"</span><span className="hl-delim">{'}}'}</span>...<span className="hl-delim">{'{{'}</span><span className="hl-keyword">end</span><span className="hl-delim">{'}}'}</span></div>
                  </div>
                  <div className="border-b border-base-content/10 pb-2">
                    <div className="text-base-content/50 mb-1">Loops:</div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-keyword">range</span> <span className="hl-var">$item</span> := <span className="hl-var">.Items</span><span className="hl-delim">{'}}'}</span>...<span className="hl-delim">{'{{'}</span><span className="hl-keyword">end</span><span className="hl-delim">{'}}'}</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-keyword">range</span> <span className="hl-var">$i</span>, <span className="hl-var">$item</span> := <span className="hl-var">.Items</span><span className="hl-delim">{'}}'}</span>...<span className="hl-delim">{'{{'}</span><span className="hl-keyword">end</span><span className="hl-delim">{'}}'}</span></div>
                  </div>
                  <div>
                    <div className="text-base-content/50 mb-1">Scoping:</div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-keyword">with</span> <span className="hl-var">.Author</span><span className="hl-delim">{'}}'}</span><span className="hl-delim">{'{{'}</span><span className="hl-var">.Name</span><span className="hl-delim">{'}}'}</span><span className="hl-delim">{'{{'}</span><span className="hl-keyword">end</span><span className="hl-delim">{'}}'}</span></div>
                  </div>
                </div>
              </div>

              {/* Site Functions */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Site Functions</h4>
                <div className="hl-code font-mono text-xs space-y-1">
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">site_name</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Site name from settings</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">site_description</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Site description</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">user</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Current logged-in user (or nil)</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">path</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Current URL path</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">query</span> <span className="hl-string">"param"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Get query parameter</span></div>
                </div>
              </div>

              {/* Data Functions */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Data Functions</h4>
                <div className="hl-code font-mono text-xs space-y-2">
                  <div className="border-b border-base-content/10 pb-2">
                    <div className="text-base-content/50 mb-1">Pages:</div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">pages</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- All root pages</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">pages</span> <span className="hl-string">"parent-id"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Child pages</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">published_pages</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Published pages only</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">page</span> <span className="hl-string">"about"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Get page by ID</span></div>
                  </div>
                  <div className="border-b border-base-content/10 pb-2">
                    <div className="text-base-content/50 mb-1">Collections:</div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">documents</span> <span className="hl-string">"posts"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- All documents</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">documents</span> <span className="hl-string">"posts"</span> <span className="hl-string">"ORDER BY CreatedAt DESC"</span><span className="hl-delim">{'}}'}</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">document</span> <span className="hl-string">"doc-id"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Single document</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">collection</span> <span className="hl-string">"posts"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Collection info</span></div>
                  </div>
                  <div>
                    <div className="text-base-content/50 mb-1">Partials:</div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">partial</span> <span className="hl-string">"header"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Include partial</span></div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">partial</span> <span className="hl-string">"nav"</span> <span className="hl-var">.</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- With context</span></div>
                  </div>
                </div>
              </div>

              {/* Document Fields */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Document Fields</h4>
                <div className="hl-code font-mono text-xs space-y-1">
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-var">$doc</span><span className="hl-var">.GetString</span> <span className="hl-string">"title"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Get text field</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-var">$doc</span><span className="hl-var">.GetInt</span> <span className="hl-string">"count"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Get number</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-var">$doc</span><span className="hl-var">.GetBool</span> <span className="hl-string">"published"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Get boolean</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-var">$doc</span><span className="hl-var">.GetTime</span> <span className="hl-string">"date"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Get date/time</span></div>
                </div>
              </div>

              {/* String Functions */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">String Functions</h4>
                <div className="hl-code font-mono text-xs space-y-1">
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">upper</span> <span className="hl-var">.Name</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-func">lower</span> <span className="hl-var">.Name</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-func">title</span> <span className="hl-var">.Name</span><span className="hl-delim">{'}}'}</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">truncate</span> <span className="hl-number">100</span> <span className="hl-var">.Description</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Truncate with "..."</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">substr</span> <span className="hl-var">.Text</span> <span className="hl-number">0</span> <span className="hl-number">50</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Substring</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">trim</span> <span className="hl-var">.Text</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Remove whitespace</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">replace</span> <span className="hl-string">"_"</span> <span className="hl-string">" "</span> <span className="hl-var">.Status</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Replace text</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">contains</span> <span className="hl-var">.Tags</span> <span className="hl-string">"featured"</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Check contains</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">join</span> <span className="hl-var">.Items</span> <span className="hl-string">", "</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Join array</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-var">.Content</span> | <span className="hl-func">html</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Render as HTML</span></div>
                </div>
              </div>

              {/* Math & Numbers */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Math & Numbers</h4>
                <div className="hl-code font-mono text-xs space-y-1">
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">add</span> <span className="hl-number">1</span> <span className="hl-number">2</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-func">sub</span> <span className="hl-number">5</span> <span className="hl-number">3</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-func">mul</span> <span className="hl-number">2</span> <span className="hl-number">4</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-func">div</span> <span className="hl-number">10</span> <span className="hl-number">2</span><span className="hl-delim">{'}}'}</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">mod</span> <span className="hl-number">7</span> <span className="hl-number">3</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Modulo</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">seq</span> <span className="hl-number">1</span> <span className="hl-number">10</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Sequence 1 to 10</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">number</span> <span className="hl-number">1234567</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Format: 1,234,567</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">currency</span> <span className="hl-number">99.99</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Format: $99.99</span></div>
                </div>
              </div>

              {/* Date & Time */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Date & Time</h4>
                <div className="hl-code font-mono text-xs space-y-1">
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">now</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Current time</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">date</span> <span className="hl-string">"2006-01-02"</span> <span className="hl-var">.CreatedAt</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Format date</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-func">date</span> <span className="hl-string">"Jan 2, 2006"</span> <span className="hl-var">.CreatedAt</span><span className="hl-delim">{'}}'}</span></div>
                  <div className="text-base-content/50 mt-1">Use Go reference time: Mon Jan 2 15:04:05 MST 2006</div>
                </div>
              </div>

              {/* Comparisons */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Comparisons</h4>
                <div className="hl-code font-mono text-xs space-y-1">
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-builtin">eq</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Equal</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-builtin">ne</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Not equal</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-builtin">lt</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-builtin">le</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Less than (or equal)</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-builtin">gt</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-builtin">ge</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> <span className="text-base-content/50">- Greater than (or equal)</span></div>
                  <div><span className="hl-delim">{'{{'}</span><span className="hl-builtin">and</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-builtin">or</span> <span className="hl-var">.A</span> <span className="hl-var">.B</span><span className="hl-delim">{'}}'}</span> / <span className="hl-delim">{'{{'}</span><span className="hl-builtin">not</span> <span className="hl-var">.A</span><span className="hl-delim">{'}}'}</span></div>
                </div>
              </div>

              {/* Common Patterns */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">Common Patterns</h4>
                <div className="hl-code font-mono text-xs space-y-3">
                  <div>
                    <div className="text-base-content/50 mb-1">Navigation menu:</div>
                    <pre className="text-xs whitespace-pre-wrap"><span className="hl-delim">{'{{'}</span><span className="hl-keyword">range</span> <span className="hl-var">$p</span> := <span className="hl-func">published_pages</span><span className="hl-delim">{'}}'}</span>{'\n'}  <span className="hl-tag">&lt;a</span> <span className="hl-attr">href</span>=<span className="hl-string">"</span><span className="hl-delim">{'{{'}</span><span className="hl-var">$p</span><span className="hl-var">.Path</span><span className="hl-delim">{'}}'}</span><span className="hl-string">"</span><span className="hl-tag">&gt;</span><span className="hl-delim">{'{{'}</span><span className="hl-var">$p</span><span className="hl-var">.Title</span><span className="hl-delim">{'}}'}</span><span className="hl-tag">&lt;/a&gt;</span>{'\n'}<span className="hl-delim">{'{{'}</span><span className="hl-keyword">end</span><span className="hl-delim">{'}}'}</span></pre>
                  </div>
                  <div>
                    <div className="text-base-content/50 mb-1">Blog posts:</div>
                    <pre className="text-xs whitespace-pre-wrap"><span className="hl-delim">{'{{'}</span><span className="hl-keyword">range</span> <span className="hl-var">$post</span> := <span className="hl-func">documents</span> <span className="hl-string">"posts"</span> <span className="hl-string">"ORDER BY CreatedAt DESC"</span><span className="hl-delim">{'}}'}</span>{'\n'}  <span className="hl-tag">&lt;article&gt;</span>{'\n'}    <span className="hl-tag">&lt;h2&gt;</span><span className="hl-delim">{'{{'}</span><span className="hl-var">$post</span><span className="hl-var">.GetString</span> <span className="hl-string">"title"</span><span className="hl-delim">{'}}'}</span><span className="hl-tag">&lt;/h2&gt;</span>{'\n'}    <span className="hl-tag">&lt;p&gt;</span><span className="hl-delim">{'{{'}</span><span className="hl-func">truncate</span> <span className="hl-number">200</span> (<span className="hl-var">$post</span><span className="hl-var">.GetString</span> <span className="hl-string">"content"</span>)<span className="hl-delim">{'}}'}</span><span className="hl-tag">&lt;/p&gt;</span>{'\n'}  <span className="hl-tag">&lt;/article&gt;</span>{'\n'}<span className="hl-delim">{'{{'}</span><span className="hl-keyword">end</span><span className="hl-delim">{'}}'}</span></pre>
                  </div>
                  <div>
                    <div className="text-base-content/50 mb-1">Default value:</div>
                    <div><span className="hl-delim">{'{{'}</span><span className="hl-func">default</span> <span className="hl-string">"Untitled"</span> <span className="hl-var">.Title</span><span className="hl-delim">{'}}'}</span></div>
                  </div>
                </div>
              </div>

              {/* JavaScript API */}
              <div>
                <h4 className="font-semibold text-sm mb-2 text-primary">JavaScript API (for forms)</h4>
                <div className="hl-code font-mono text-xs space-y-2">
                  <div className="text-base-content/50 mb-1">Submit form data to a collection:</div>
                  <pre className="text-xs whitespace-pre-wrap text-base-content/80">{`<form onsubmit="submitForm(event, 'ideas')">
  <input name="title" required>
  <textarea name="content"></textarea>
  <button type="submit">Submit</button>
</form>

<script>
async function submitForm(e, collection) {
  e.preventDefault();
  const data = Object.fromEntries(new FormData(e.target));
  const res = await fetch('/api/collections/' + collection + '/records', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  if (res.ok) {
    e.target.reset();
    alert('Submitted!');
  }
}
</script>`}</pre>
                  <div className="border-t border-base-content/10 pt-2 mt-2">
                    <div className="text-base-content/50 mb-1">API Endpoints:</div>
                    <div className="space-y-1">
                      <div><span className="hl-keyword">GET</span> <span className="hl-string">/api/collections/{'{id}'}/records</span> <span className="text-base-content/50">- List records</span></div>
                      <div><span className="hl-keyword">POST</span> <span className="hl-string">/api/collections/{'{id}'}/records</span> <span className="text-base-content/50">- Create record</span></div>
                      <div><span className="hl-keyword">PATCH</span> <span className="hl-string">/api/collections/{'{id}'}/records/{'{recordId}'}</span> <span className="text-base-content/50">- Update</span></div>
                      <div><span className="hl-keyword">DELETE</span> <span className="hl-string">/api/collections/{'{id}'}/records/{'{recordId}'}</span> <span className="text-base-content/50">- Delete</span></div>
                    </div>
                  </div>
                </div>
              </div>

            </div>
            <div className="flex justify-end gap-2 mt-4 pt-4 border-t border-base-300">
              <button type="button" className="btn btn-ghost btn-sm" onClick={() => setShowTemplateHelp(false)}>Close</button>
            </div>
          </div>
          <div className="modal-backdrop" onClick={() => setShowTemplateHelp(false)} />
        </dialog>
      )}
    </div>
  );
}
← Back