Mercurial
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 200:90dfcef375fb | 201:6cdee35a7ba9 |
|---|---|
| 1 <!DOCTYPE html> | |
| 2 <html lang="en"> | |
| 3 <head> | |
| 4 {{/parts/base_head.html}} | |
| 5 <title>Editor | MrJuneJune</title> | |
| 6 <style> | |
| 7 .editor-page { | |
| 8 max-width: 900px; | |
| 9 margin: 0 auto; | |
| 10 padding: 20px; | |
| 11 } | |
| 12 .editor-header { | |
| 13 display: flex; | |
| 14 justify-content: space-between; | |
| 15 align-items: center; | |
| 16 margin-bottom: 20px; | |
| 17 } | |
| 18 .editor-header h1 { | |
| 19 margin: 0; | |
| 20 } | |
| 21 .doc-selector { | |
| 22 display: flex; | |
| 23 gap: 10px; | |
| 24 align-items: center; | |
| 25 } | |
| 26 .doc-selector input { | |
| 27 padding: 8px 12px; | |
| 28 border: 1px solid #ccc; | |
| 29 border-radius: 4px; | |
| 30 font-size: 14px; | |
| 31 } | |
| 32 .doc-selector button { | |
| 33 padding: 8px 16px; | |
| 34 background: #0078ff; | |
| 35 color: white; | |
| 36 border: none; | |
| 37 border-radius: 4px; | |
| 38 cursor: pointer; | |
| 39 } | |
| 40 .doc-selector button:hover { | |
| 41 background: #0066dd; | |
| 42 } | |
| 43 .auth-section { | |
| 44 margin-bottom: 20px; | |
| 45 padding: 16px; | |
| 46 background: #f5f5f5; | |
| 47 border-radius: 4px; | |
| 48 } | |
| 49 .auth-section input { | |
| 50 width: 300px; | |
| 51 padding: 8px 12px; | |
| 52 border: 1px solid #ccc; | |
| 53 border-radius: 4px; | |
| 54 font-size: 14px; | |
| 55 } | |
| 56 .auth-section label { | |
| 57 display: block; | |
| 58 margin-bottom: 8px; | |
| 59 font-weight: bold; | |
| 60 } | |
| 61 </style> | |
| 62 </head> | |
| 63 <body> | |
| 64 {{/parts/header.html}} | |
| 65 | |
| 66 <main class="editor-page"> | |
| 67 <div class="editor-header"> | |
| 68 <h1>Rich Editor</h1> | |
| 69 <div class="doc-selector"> | |
| 70 <input type="text" id="doc-id" placeholder="Document ID" value="default"> | |
| 71 <button onclick="loadDocument()">Load</button> | |
| 72 </div> | |
| 73 </div> | |
| 74 | |
| 75 <div class="auth-section"> | |
| 76 <label for="auth-token">Auth Token:</label> | |
| 77 <input type="password" id="auth-token" placeholder="Enter your auth token"> | |
| 78 </div> | |
| 79 | |
| 80 <div id="editor-container"></div> | |
| 81 </main> | |
| 82 | |
| 83 {{/parts/footer.html}} | |
| 84 | |
| 85 <script src="/public/js/rich_editor.js"></script> | |
| 86 <script> | |
| 87 let editor = null; | |
| 88 | |
| 89 function getAuthToken() { | |
| 90 return document.getElementById('auth-token').value; | |
| 91 } | |
| 92 | |
| 93 function getDocId() { | |
| 94 return document.getElementById('doc-id').value || 'default'; | |
| 95 } | |
| 96 | |
| 97 async function uploadFile(file) { | |
| 98 const token = getAuthToken(); | |
| 99 if (!token) { | |
| 100 alert('Please enter your auth token'); | |
| 101 throw new Error('No auth token'); | |
| 102 } | |
| 103 | |
| 104 // Get presigned upload URL | |
| 105 const response = await fetch('/api/s3/upload-url', { | |
| 106 method: 'POST', | |
| 107 headers: { | |
| 108 'Authorization': 'Bearer ' + token, | |
| 109 'Content-Type': 'application/json' | |
| 110 }, | |
| 111 body: JSON.stringify({ | |
| 112 filename: file.name, | |
| 113 content_type: file.type | |
| 114 }) | |
| 115 }); | |
| 116 | |
| 117 if (!response.ok) { | |
| 118 const error = await response.json(); | |
| 119 throw new Error(error.error || 'Failed to get upload URL'); | |
| 120 } | |
| 121 | |
| 122 const data = await response.json(); | |
| 123 | |
| 124 // Upload file directly to S3 | |
| 125 const uploadResponse = await fetch(data.upload_url, { | |
| 126 method: 'PUT', | |
| 127 headers: { | |
| 128 'Content-Type': file.type | |
| 129 }, | |
| 130 body: file | |
| 131 }); | |
| 132 | |
| 133 if (!uploadResponse.ok) { | |
| 134 throw new Error('Failed to upload file to S3'); | |
| 135 } | |
| 136 | |
| 137 return { | |
| 138 url: data.public_url, | |
| 139 key: data.key | |
| 140 }; | |
| 141 } | |
| 142 | |
| 143 async function saveContent(content) { | |
| 144 const token = getAuthToken(); | |
| 145 if (!token) { | |
| 146 console.warn('No auth token, skipping save'); | |
| 147 return; | |
| 148 } | |
| 149 | |
| 150 const response = await fetch('/api/editor/save', { | |
| 151 method: 'POST', | |
| 152 headers: { | |
| 153 'Authorization': 'Bearer ' + token, | |
| 154 'Content-Type': 'application/json' | |
| 155 }, | |
| 156 body: JSON.stringify({ | |
| 157 doc_id: getDocId(), | |
| 158 content: content | |
| 159 }) | |
| 160 }); | |
| 161 | |
| 162 if (!response.ok) { | |
| 163 const error = await response.json(); | |
| 164 throw new Error(error.error || 'Failed to save'); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 async function loadDocument() { | |
| 169 const token = getAuthToken(); | |
| 170 if (!token) { | |
| 171 alert('Please enter your auth token'); | |
| 172 return; | |
| 173 } | |
| 174 | |
| 175 const docId = getDocId(); | |
| 176 | |
| 177 try { | |
| 178 const response = await fetch('/api/editor/load/' + encodeURIComponent(docId), { | |
| 179 headers: { | |
| 180 'Authorization': 'Bearer ' + token | |
| 181 } | |
| 182 }); | |
| 183 | |
| 184 if (!response.ok) { | |
| 185 const error = await response.json(); | |
| 186 alert('Error: ' + (error.error || 'Failed to load')); | |
| 187 return; | |
| 188 } | |
| 189 | |
| 190 const data = await response.json(); | |
| 191 editor.setContent(data.content || ''); | |
| 192 console.log('Loaded document:', docId); | |
| 193 } catch (error) { | |
| 194 console.error('Load error:', error); | |
| 195 alert('Failed to load document'); | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 // Initialize editor | |
| 200 document.addEventListener('DOMContentLoaded', function() { | |
| 201 editor = RichEditor.init('editor-container', { | |
| 202 uploadCallback: uploadFile, | |
| 203 saveCallback: saveContent, | |
| 204 debounceMs: 1500, | |
| 205 placeholder: 'Start writing... (paste images, use /upload for files)' | |
| 206 }); | |
| 207 | |
| 208 // Try to load saved token from localStorage | |
| 209 const savedToken = localStorage.getItem('editor-auth-token'); | |
| 210 if (savedToken) { | |
| 211 document.getElementById('auth-token').value = savedToken; | |
| 212 } | |
| 213 | |
| 214 // Save token to localStorage on change | |
| 215 document.getElementById('auth-token').addEventListener('change', function() { | |
| 216 localStorage.setItem('editor-auth-token', this.value); | |
| 217 }); | |
| 218 }); | |
| 219 </script> | |
| 220 </body> | |
| 221 </html> |