|
201
|
1 console.log("june");
|
|
|
2
|
|
|
3 let editor = null;
|
|
|
4 let currentNoteId = 'index';
|
|
|
5
|
|
|
6 function getAuthToken() {
|
|
|
7 return localStorage.getItem('notes-auth-token');
|
|
|
8 }
|
|
|
9
|
|
|
10 function requireAuth() {
|
|
|
11 if (!getAuthToken()) {
|
|
|
12 const returnUrl = encodeURIComponent(window.location.pathname);
|
|
|
13 window.location.href = '/notes/login?return=' + returnUrl;
|
|
|
14 return false;
|
|
|
15 }
|
|
|
16 return true;
|
|
|
17 }
|
|
|
18
|
|
|
19 function logout() {
|
|
|
20 localStorage.removeItem('notes-auth-token');
|
|
|
21 window.location.href = '/notes/login';
|
|
|
22 }
|
|
|
23
|
|
|
24 function getNoteIdFromPath() {
|
|
|
25 const path = window.location.pathname;
|
|
|
26 const match = path.match(/^\/notes\/(.+)$/);
|
|
|
27 if (match && match[1] && match[1] !== 'login') {
|
|
|
28 return decodeURIComponent(match[1]);
|
|
|
29 }
|
|
|
30 return 'index';
|
|
|
31 }
|
|
|
32
|
|
|
33 function showNewNoteDialog() {
|
|
|
34 document.getElementById('new-note-dialog').classList.add('show');
|
|
|
35 document.getElementById('new-note-id').focus();
|
|
|
36 }
|
|
|
37
|
|
|
38 function hideNewNoteDialog() {
|
|
|
39 document.getElementById('new-note-dialog').classList.remove('show');
|
|
|
40 document.getElementById('new-note-id').value = '';
|
|
|
41 }
|
|
|
42
|
|
|
43 function createNewNote() {
|
|
|
44 let noteId = document.getElementById('new-note-id').value.trim();
|
|
|
45 if (!noteId) return;
|
|
|
46
|
|
|
47 // Sanitize note ID
|
|
|
48 noteId = noteId.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
|
|
|
49
|
|
|
50 hideNewNoteDialog();
|
|
|
51 window.location.href = '/notes/' + encodeURIComponent(noteId);
|
|
|
52 }
|
|
|
53
|
|
|
54 // Handle Enter key in new note dialog
|
|
|
55 document.getElementById('new-note-id').addEventListener('keydown', function(e) {
|
|
|
56 if (e.key === 'Enter') {
|
|
|
57 e.preventDefault();
|
|
|
58 createNewNote();
|
|
|
59 }
|
|
|
60 if (e.key === 'Escape') {
|
|
|
61 hideNewNoteDialog();
|
|
|
62 }
|
|
|
63 });
|
|
|
64
|
|
|
65 // Close dialog on backdrop click
|
|
|
66 document.getElementById('new-note-dialog').addEventListener('click', function(e) {
|
|
|
67 if (e.target === this) {
|
|
|
68 hideNewNoteDialog();
|
|
|
69 }
|
|
|
70 });
|
|
|
71
|
|
|
72 async function uploadFile(file) {
|
|
|
73 const token = getAuthToken();
|
|
|
74 if (!token) {
|
|
|
75 throw new Error('Not authenticated');
|
|
|
76 }
|
|
|
77
|
|
|
78 // 1. Create media record
|
|
|
79 const createResp = await fetch('/api/media/create', {
|
|
|
80 method: 'POST',
|
|
|
81 headers: {
|
|
|
82 'Authorization': 'Bearer ' + token,
|
|
|
83 'Content-Type': 'application/json'
|
|
|
84 },
|
|
|
85 body: JSON.stringify({
|
|
|
86 filename: file.name,
|
|
|
87 content_type: file.type
|
|
|
88 })
|
|
|
89 });
|
|
|
90
|
|
|
91 if (!createResp.ok) {
|
|
|
92 const error = await createResp.json();
|
|
|
93 throw new Error(error.error || 'Failed to create media record');
|
|
|
94 }
|
|
|
95
|
|
|
96 const { media_id, upload_url } = await createResp.json();
|
|
|
97
|
|
|
98 // 2. Upload to S3
|
|
|
99 const uploadResp = await fetch(upload_url, {
|
|
|
100 method: 'PUT',
|
|
|
101 headers: { 'Content-Type': file.type },
|
|
|
102 body: file
|
|
|
103 });
|
|
|
104
|
|
|
105 if (!uploadResp.ok) {
|
|
|
106 throw new Error('S3 upload failed');
|
|
|
107 }
|
|
|
108
|
|
|
109 // 3. Mark uploaded
|
|
|
110 await fetch(`/api/media/${media_id}/uploaded`, {
|
|
|
111 method: 'POST',
|
|
|
112 headers: { 'Authorization': 'Bearer ' + token }
|
|
|
113 });
|
|
|
114
|
|
|
115 // 4. Poll for images, immediate return for non-images
|
|
|
116 if (file.type.startsWith('image/')) {
|
|
|
117 return await pollForProcessedImage(media_id);
|
|
|
118 } else {
|
|
|
119 // For non-images, return the original S3 URL
|
|
|
120 const s3_url = upload_url.split('?')[0];
|
|
|
121 return { url: s3_url };
|
|
|
122 }
|
|
|
123 }
|
|
|
124
|
|
|
125 async function pollForProcessedImage(mediaId) {
|
|
|
126 const token = getAuthToken();
|
|
|
127 const maxAttempts = 60; // 2 minutes max
|
|
|
128
|
|
|
129 for (let i = 0; i < maxAttempts; i++) {
|
|
|
130 await new Promise(r => setTimeout(r, 2000)); // 2 sec interval
|
|
|
131
|
|
|
132 const resp = await fetch(`/api/media/${mediaId}/status`, {
|
|
|
133 headers: { 'Authorization': 'Bearer ' + token }
|
|
|
134 });
|
|
|
135
|
|
|
136 if (!resp.ok) continue;
|
|
|
137
|
|
|
138 const { status, processed_url, error_message } = await resp.json();
|
|
|
139
|
|
|
140 if (status === 'finished') return { url: processed_url };
|
|
|
141 if (status === 'error') throw new Error(error_message || 'Processing failed');
|
|
|
142 }
|
|
|
143
|
|
|
144 throw new Error('Processing timeout');
|
|
|
145 }
|
|
|
146
|
|
|
147 async function saveContent(content) {
|
|
|
148 const token = getAuthToken();
|
|
|
149 if (!token) return;
|
|
|
150
|
|
|
151 const response = await fetch('/api/editor/save', {
|
|
|
152 method: 'POST',
|
|
|
153 headers: {
|
|
|
154 'Authorization': 'Bearer ' + token,
|
|
|
155 'Content-Type': 'application/json'
|
|
|
156 },
|
|
|
157 body: JSON.stringify({
|
|
|
158 doc_id: currentNoteId,
|
|
|
159 content: content
|
|
|
160 })
|
|
|
161 });
|
|
|
162
|
|
|
163 if (!response.ok) {
|
|
|
164 throw new Error('Failed to save');
|
|
|
165 }
|
|
|
166 }
|
|
|
167
|
|
|
168 async function loadNote(noteId) {
|
|
|
169 const token = getAuthToken();
|
|
|
170 if (!token) return;
|
|
|
171
|
|
|
172 try {
|
|
|
173 const response = await fetch('/api/editor/load/' + encodeURIComponent(noteId), {
|
|
|
174 headers: { 'Authorization': 'Bearer ' + token }
|
|
|
175 });
|
|
|
176
|
|
|
177 if (response.ok) {
|
|
|
178 const data = await response.json();
|
|
|
179 editor.setContent(data.content || '');
|
|
|
180 }
|
|
|
181 } catch (error) {
|
|
|
182 console.error('Failed to load note:', error);
|
|
|
183 }
|
|
|
184 }
|
|
|
185
|
|
|
186 // Initialize
|
|
|
187 document.addEventListener('DOMContentLoaded', function() {
|
|
|
188 if (!requireAuth()) return;
|
|
|
189
|
|
|
190 currentNoteId = getNoteIdFromPath();
|
|
|
191 document.getElementById('note-id-display').textContent = currentNoteId;
|
|
|
192
|
|
|
193 // Update page title
|
|
|
194 document.title = currentNoteId + ' | Notes';
|
|
|
195
|
|
|
196 editor = RichEditor.init('editor-container', {
|
|
|
197 uploadCallback: uploadFile,
|
|
|
198 saveCallback: saveContent,
|
|
|
199 debounceMs: 1500,
|
|
|
200 placeholder: 'Start writing... (paste images, drag files, or use /upload)\n\nTip: Click "+ New Note" to create linked notes.'
|
|
|
201 });
|
|
|
202
|
|
|
203 loadNote(currentNoteId);
|
|
|
204 });
|