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>