comparison mrjunejune/src/notes/index.html @ 202:b9b184b3303c

[Notes] Images get processed and it is properly fetched. Thank you.
author MrJuneJune <me@mrjunejune.com>
date Sun, 15 Feb 2026 09:12:57 -0800
parents 6cdee35a7ba9
children e5aed6c36672
comparison
equal deleted inserted replaced
201:6cdee35a7ba9 202:b9b184b3303c
156 </div> 156 </div>
157 157
158 {{/parts/footer.html}} 158 {{/parts/footer.html}}
159 159
160 <script src="/public/js/rich_editor.js"></script> 160 <script src="/public/js/rich_editor.js"></script>
161 <script> 161 <script src="/public/editor.js"></script>
162
163 let editor = null;
164 let currentNoteId = 'index';
165
166 function getAuthToken() {
167 return localStorage.getItem('notes-auth-token');
168 }
169
170 function requireAuth() {
171 if (!getAuthToken()) {
172 const returnUrl = encodeURIComponent(window.location.pathname);
173 window.location.href = '/notes/login?return=' + returnUrl;
174 return false;
175 }
176 return true;
177 }
178
179 function logout() {
180 localStorage.removeItem('notes-auth-token');
181 window.location.href = '/notes/login';
182 }
183
184 function getNoteIdFromPath() {
185 const path = window.location.pathname;
186 const match = path.match(/^\/notes\/(.+)$/);
187 if (match && match[1] && match[1] !== 'login') {
188 return decodeURIComponent(match[1]);
189 }
190 return 'index';
191 }
192
193 function showNewNoteDialog() {
194 document.getElementById('new-note-dialog').classList.add('show');
195 document.getElementById('new-note-id').focus();
196 }
197
198 function hideNewNoteDialog() {
199 document.getElementById('new-note-dialog').classList.remove('show');
200 document.getElementById('new-note-id').value = '';
201 }
202
203 function createNewNote() {
204 let noteId = document.getElementById('new-note-id').value.trim();
205 if (!noteId) return;
206
207 // Sanitize note ID
208 noteId = noteId.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
209
210 hideNewNoteDialog();
211 window.location.href = '/notes/' + encodeURIComponent(noteId);
212 }
213
214 // Handle Enter key in new note dialog
215 document.getElementById('new-note-id').addEventListener('keydown', function(e) {
216 if (e.key === 'Enter') {
217 e.preventDefault();
218 createNewNote();
219 }
220 if (e.key === 'Escape') {
221 hideNewNoteDialog();
222 }
223 });
224
225 // Close dialog on backdrop click
226 document.getElementById('new-note-dialog').addEventListener('click', function(e) {
227 if (e.target === this) {
228 hideNewNoteDialog();
229 }
230 });
231
232 async function uploadFile(file) {
233 const token = getAuthToken();
234 if (!token) {
235 throw new Error('Not authenticated');
236 }
237
238 // Get s3 bucket URL
239 const response = await fetch('/api/s3/upload-url', {
240 method: 'POST',
241 headers: {
242 'Authorization': 'Bearer ' + token,
243 'Content-Type': 'application/json'
244 },
245 body: JSON.stringify({
246 filename: file.name,
247 content_type: file.type
248 })
249 });
250
251 if (!response.ok) {
252 const error = await response.json();
253 throw new Error(error.error || 'Failed to get upload URL');
254 }
255
256 const data = await response.json();
257
258 const uploadResponse = await fetch(data.upload_url, {
259 method: 'PUT',
260 headers: { 'Content-Type': file.type },
261 body: file
262 });
263
264 if (!uploadResponse.ok) {
265 throw new Error('Failed to upload file to S3');
266 }
267
268 return { url: data.public_url, key: data.key };
269 }
270
271 async function saveContent(content) {
272 const token = getAuthToken();
273 if (!token) return;
274
275 const response = await fetch('/api/editor/save', {
276 method: 'POST',
277 headers: {
278 'Authorization': 'Bearer ' + token,
279 'Content-Type': 'application/json'
280 },
281 body: JSON.stringify({
282 doc_id: currentNoteId,
283 content: content
284 })
285 });
286
287 if (!response.ok) {
288 throw new Error('Failed to save');
289 }
290 }
291
292 async function loadNote(noteId) {
293 const token = getAuthToken();
294 if (!token) return;
295
296 try {
297 const response = await fetch('/api/editor/load/' + encodeURIComponent(noteId), {
298 headers: { 'Authorization': 'Bearer ' + token }
299 });
300
301 if (response.ok) {
302 const data = await response.json();
303 editor.setContent(data.content || '');
304 }
305 } catch (error) {
306 console.error('Failed to load note:', error);
307 }
308 }
309
310 // Initialize
311 document.addEventListener('DOMContentLoaded', function() {
312 if (!requireAuth()) return;
313
314 currentNoteId = getNoteIdFromPath();
315 document.getElementById('note-id-display').textContent = currentNoteId;
316
317 // Update page title
318 document.title = currentNoteId + ' | Notes';
319
320 editor = RichEditor.init('editor-container', {
321 uploadCallback: uploadFile,
322 saveCallback: saveContent,
323 debounceMs: 1500,
324 placeholder: 'Start writing... (paste images, drag files, or use /upload)\n\nTip: Click "+ New Note" to create linked notes.'
325 });
326
327 loadNote(currentNoteId);
328 });
329 </script>
330 </body> 162 </body>
331 </html> 163 </html>