view mrjunejune/src/public/editor.js @ 215:c3df85159b31

removed a python library that isn't used for much.
author June Park <parkjune1995@gmail.com>
date Sat, 28 Feb 2026 20:34:18 -0800
parents e5aed6c36672
children
line wrap: on
line source

let editor = null;
let currentNoteId = 'index';

function getAuthToken() {
  return localStorage.getItem('notes-auth-token');
}

function requireAuth() {
  if (!getAuthToken()) {
    const returnUrl = encodeURIComponent(window.location.pathname);
    window.location.href = '/notes/login?return=' + returnUrl;
    return false;
  }
  return true;
}

function goHome() {
  window.location.href = '/notes';
}

function logout() {
  localStorage.removeItem('notes-auth-token');
  window.location.href = '/notes/login';
}

function getNoteIdFromPath() {
  const path = window.location.pathname;
  const match = path.match(/^\/notes\/(.+)$/);
  if (match && match[1] && match[1] !== 'login') {
    return decodeURIComponent(match[1]);
  }
  return 'index';
}

function showNewNoteDialog() {
  document.getElementById('new-note-dialog').classList.add('show');
  document.getElementById('new-note-id').focus();
}

function hideNewNoteDialog() {
  document.getElementById('new-note-dialog').classList.remove('show');
  document.getElementById('new-note-id').value = '';
}

function createNewNote() {
  let noteId = document.getElementById('new-note-id').value.trim();
  if (!noteId) return;

  // Sanitize note ID
  noteId = noteId.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');

  hideNewNoteDialog();
  window.location.href = '/notes/' + encodeURIComponent(noteId);
}

// Handle Enter key in new note dialog
document.getElementById('new-note-id').addEventListener('keydown', function(e) {
  if (e.key === 'Enter') {
    e.preventDefault();
    createNewNote();
  }
  if (e.key === 'Escape') {
    hideNewNoteDialog();
  }
});

// Close dialog on backdrop click
document.getElementById('new-note-dialog').addEventListener('click', function(e) {
  if (e.target === this) {
    hideNewNoteDialog();
  }
});

async function uploadFile(file) {
  const token = getAuthToken();
  if (!token) {
    throw new Error('Not authenticated');
  }

  // 1. Create media record
  const createResponse = await fetch('/api/media/create', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + token,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      filename: file.name,
      content_type: file.type
    })
  });

  if (!createResponse.ok) {
    const error = await createResponse.json().catch(() => ({}));
    throw new Error(error.error || 'Failed to create media record');
  }

  const data = await createResponse.json();

  // 2. 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');
  }

  // 3. Mark as uploaded (triggers processing for images)
  await fetch(`/api/media/${data.media_id}/uploaded`, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + token
    }
  });

  // 4. Poll for images, return immediately for non-images
  if (file.type.startsWith('image/')) {
    return await pollForProcessedImage(data.media_id, token);
  } else {
    // For non-images, use the public URL from the server
    return { url: data.public_url };
  }
}

async function pollForProcessedImage(mediaId, token) {
  const maxAttempts = 60; // 2 minutes max (60 * 2 seconds)

  for (let i = 0; i < maxAttempts; i++) {
    await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second interval

    const statusResponse = await fetch(`/api/media/${mediaId}/status`, {
      headers: {
        'Authorization': 'Bearer ' + token
      }
    });

    if (!statusResponse.ok) {
      console.warn('Status check failed, retrying...');
      continue;
    }

    const statusData = await statusResponse.json();

    if (statusData.status === 'finished') {
      return { url: statusData.processed_url };
    } else if (statusData.status === 'error') {
      throw new Error(statusData.error_message || 'Processing failed');
    }
    // Status is 'uploaded' or 'processing', continue polling
  }

  throw new Error('Processing timeout after 2 minutes');
}

async function saveContent(content) {
  const token = getAuthToken();
  if (!token) return;

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

  if (!response.ok) {
    throw new Error('Failed to save');
  }
}

async function loadNote(noteId) {
  const token = getAuthToken();
  if (!token) return;

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

    if (response.ok) {
      const data = await response.json();
      editor.setContent(data.content || '');
    }
  } catch (error) {
    console.error('Failed to load note:', error);
  }
}

// Initialize
document.addEventListener('DOMContentLoaded', function() {
  if (!requireAuth()) return;

  currentNoteId = getNoteIdFromPath();
  document.getElementById('note-id-display').textContent = currentNoteId;

  // Update page title
  document.title = currentNoteId + ' | Notes';

  editor = RichEditor.init('editor-container', {
    uploadCallback: uploadFile,
    saveCallback: saveContent,
    debounceMs: 1500,
    placeholder: 'Start writing... (paste images, drag files, or use /upload)\n\nTip: Click "+ New Note" to create linked notes.'
  });

  loadNote(currentNoteId);
});