view mrjunejune/src/editor/index.html @ 201:6cdee35a7ba9

[MrJuneJune] notes
author MrJuneJune <me@mrjunejune.com>
date Sun, 15 Feb 2026 07:07:50 -0800
parents
children
line wrap: on
line source

<!DOCTYPE html>
<html lang="en">
<head>
  {{/parts/base_head.html}}
  <title>Editor | MrJuneJune</title>
  <style>
    .editor-page {
      max-width: 900px;
      margin: 0 auto;
      padding: 20px;
    }
    .editor-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
    }
    .editor-header h1 {
      margin: 0;
    }
    .doc-selector {
      display: flex;
      gap: 10px;
      align-items: center;
    }
    .doc-selector input {
      padding: 8px 12px;
      border: 1px solid #ccc;
      border-radius: 4px;
      font-size: 14px;
    }
    .doc-selector button {
      padding: 8px 16px;
      background: #0078ff;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .doc-selector button:hover {
      background: #0066dd;
    }
    .auth-section {
      margin-bottom: 20px;
      padding: 16px;
      background: #f5f5f5;
      border-radius: 4px;
    }
    .auth-section input {
      width: 300px;
      padding: 8px 12px;
      border: 1px solid #ccc;
      border-radius: 4px;
      font-size: 14px;
    }
    .auth-section label {
      display: block;
      margin-bottom: 8px;
      font-weight: bold;
    }
  </style>
</head>
<body>
  {{/parts/header.html}}

  <main class="editor-page">
    <div class="editor-header">
      <h1>Rich Editor</h1>
      <div class="doc-selector">
        <input type="text" id="doc-id" placeholder="Document ID" value="default">
        <button onclick="loadDocument()">Load</button>
      </div>
    </div>

    <div class="auth-section">
      <label for="auth-token">Auth Token:</label>
      <input type="password" id="auth-token" placeholder="Enter your auth token">
    </div>

    <div id="editor-container"></div>
  </main>

  {{/parts/footer.html}}

  <script src="/public/js/rich_editor.js"></script>
  <script>
    let editor = null;

    function getAuthToken() {
      return document.getElementById('auth-token').value;
    }

    function getDocId() {
      return document.getElementById('doc-id').value || 'default';
    }

    async function uploadFile(file) {
      const token = getAuthToken();
      if (!token) {
        alert('Please enter your auth token');
        throw new Error('No auth token');
      }

      // Get presigned upload URL
      const response = await fetch('/api/s3/upload-url', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer ' + token,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          filename: file.name,
          content_type: file.type
        })
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error || 'Failed to get upload URL');
      }

      const data = await response.json();

      // Upload file directly to S3
      const uploadResponse = await fetch(data.upload_url, {
        method: 'PUT',
        headers: {
          'Content-Type': file.type
        },
        body: file
      });

      if (!uploadResponse.ok) {
        throw new Error('Failed to upload file to S3');
      }

      return {
        url: data.public_url,
        key: data.key
      };
    }

    async function saveContent(content) {
      const token = getAuthToken();
      if (!token) {
        console.warn('No auth token, skipping save');
        return;
      }

      const response = await fetch('/api/editor/save', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer ' + token,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          doc_id: getDocId(),
          content: content
        })
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error || 'Failed to save');
      }
    }

    async function loadDocument() {
      const token = getAuthToken();
      if (!token) {
        alert('Please enter your auth token');
        return;
      }

      const docId = getDocId();

      try {
        const response = await fetch('/api/editor/load/' + encodeURIComponent(docId), {
          headers: {
            'Authorization': 'Bearer ' + token
          }
        });

        if (!response.ok) {
          const error = await response.json();
          alert('Error: ' + (error.error || 'Failed to load'));
          return;
        }

        const data = await response.json();
        editor.setContent(data.content || '');
        console.log('Loaded document:', docId);
      } catch (error) {
        console.error('Load error:', error);
        alert('Failed to load document');
      }
    }

    // Initialize editor
    document.addEventListener('DOMContentLoaded', function() {
      editor = RichEditor.init('editor-container', {
        uploadCallback: uploadFile,
        saveCallback: saveContent,
        debounceMs: 1500,
        placeholder: 'Start writing... (paste images, use /upload for files)'
      });

      // Try to load saved token from localStorage
      const savedToken = localStorage.getItem('editor-auth-token');
      if (savedToken) {
        document.getElementById('auth-token').value = savedToken;
      }

      // Save token to localStorage on change
      document.getElementById('auth-token').addEventListener('change', function() {
        localStorage.setItem('editor-auth-token', this.value);
      });
    });
  </script>
</body>
</html>