|
201
|
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>
|