triflix commited on
Commit
01ed3be
·
verified ·
1 Parent(s): 864fc23

Update static/js/app.js

Browse files
Files changed (1) hide show
  1. static/js/app.js +120 -68
static/js/app.js CHANGED
@@ -15,6 +15,7 @@
15
  const historyList = document.getElementById("historyList");
16
  const clearHistoryBtn = document.getElementById("clearHistory");
17
  const apiBtn = document.getElementById("apiBtn");
 
18
  const apiModal = document.getElementById("apiModal");
19
  const closeApi = document.getElementById("closeApi");
20
  const copyCurl = document.getElementById("copyCurl");
@@ -38,13 +39,17 @@
38
  }
39
 
40
  // show placeholder click to open file picker
41
- placeholder.addEventListener("click", () => fileInput.click());
 
 
42
 
43
- fileInput.addEventListener("change", (ev) => {
44
- const f = ev.target.files[0];
45
- if (!f) return;
46
- selectFile(f);
47
- });
 
 
48
 
49
  function selectFile(file) {
50
  // client-side checks
@@ -62,23 +67,24 @@
62
  previewName.textContent = file.name;
63
  previewSize.textContent = humanFileSize(file.size);
64
  // preview thumb for images
65
- if (file.type.startsWith("image/")) {
66
  const url = URL.createObjectURL(file);
67
  previewThumb.style.backgroundImage = `url(${url})`;
 
68
  } else {
69
  previewThumb.style.backgroundImage = 'none';
70
- previewThumb.textContent = ext.toUpperCase();
71
  }
72
- placeholder.hidden = true;
73
- preview.hidden = false;
74
- uploadProgress.value = 0;
75
- progressText.textContent = "";
76
  }
77
 
78
  async function uploadSelectedFile(customSlug = "") {
79
  if (!currentFile) {
80
  // trigger file picker
81
- fileInput.click();
82
  return;
83
  }
84
  const fd = new FormData();
@@ -91,8 +97,8 @@
91
  xhr.upload.onprogress = (e) => {
92
  if (e.lengthComputable) {
93
  const pct = Math.floor((e.loaded / e.total) * 100);
94
- uploadProgress.value = pct;
95
- progressText.textContent = `${pct}%`;
96
  }
97
  };
98
 
@@ -120,8 +126,8 @@
120
  const err = tryParseJSON(xhr.responseText);
121
  alert((err && err.detail) ? err.detail : "Upload failed.");
122
  }
123
- progressText.textContent = "";
124
- uploadProgress.value = 0;
125
  // keep preview visible and set currentUrl for view/download
126
  if (currentSlug) {
127
  currentUrl = `/f/${currentSlug}`;
@@ -136,33 +142,42 @@
136
  }
137
 
138
  // wire upload button
139
- uploadBtn.addEventListener("click", async () => {
140
- // prompt for custom slug on long-press? For simplicity: hold Shift to allow slug
141
- let custom = "";
142
- if (window.event && window.event.shiftKey) {
143
- custom = prompt("Enter custom slug (letters, numbers, -, _ )");
144
- if (!custom) custom = "";
145
- }
146
- await uploadSelectedFile(custom);
147
- });
 
 
 
148
 
149
- copyBtn.addEventListener("click", () => {
150
- if (!currentUrl && !currentSlug) return showToast("No link yet");
151
- const u = window.location.origin + (currentUrl || `/f/${currentSlug}`);
152
- navigator.clipboard.writeText(u).then(() => showToast("Copied"));
153
- });
 
 
154
 
155
- viewBtn.addEventListener("click", () => {
156
- if (!currentUrl && !currentSlug) return showToast("No file to view");
157
- const u = (currentUrl || `/f/${currentSlug}`);
158
- window.open(u, "_blank");
159
- });
 
 
160
 
161
- dlBtn.addEventListener("click", () => {
162
- if (!currentUrl && !currentSlug) return showToast("No file to download");
163
- const u = (currentUrl || `/f/${currentSlug}`) + "?dl=1";
164
- window.open(u, "_blank");
165
- });
 
 
166
 
167
  // history in localStorage
168
  function historyKey(){ return "doto_history_v1" }
@@ -191,6 +206,7 @@
191
 
192
  function renderHistory(){
193
  const arr = getHistory();
 
194
  historyList.innerHTML = "";
195
  if (arr.length === 0) {
196
  historyList.innerHTML = "<div class='muted'>No recent uploads</div>";
@@ -199,8 +215,11 @@
199
  arr.forEach(item => {
200
  const el = document.createElement("div");
201
  el.className = "history-item";
 
 
 
202
  el.innerHTML = `
203
- <div class="history-thumb" style="${item.filename && item.filename.match(/\.(jpg|jpeg|png|gif|webp)$/i) ? 'background-image:url('+ window.location.origin + '/f/' + item.slug + ')' : ''}"></div>
204
  <div class="history-meta">
205
  <div style="font-weight:700">${escapeHtml(item.filename || item.slug)}</div>
206
  <div style="font-size:12px;color:var(--muted)">${timeAgo(item.created_at)}</div>
@@ -214,35 +233,69 @@
214
  });
215
  }
216
 
217
- historyList.addEventListener("click", (e) => {
218
- const btn = e.target.closest("button");
219
- if (!btn) return;
220
- const slug = btn.dataset.slug;
221
- const action = btn.dataset.action;
222
- if (action === "copy") {
223
- const u = window.location.origin + `/f/${slug}`;
224
- navigator.clipboard.writeText(u).then(()=> showToast("Copied"));
225
- } else if (action === "open") {
226
- window.open(`/f/${slug}`, "_blank");
227
- }
228
- });
 
 
229
 
230
- clearHistoryBtn.addEventListener("click", () => {
231
- if (confirm("Clear local history?")) clearHistory();
232
- });
 
 
233
 
234
- // modal api
235
- apiBtn.addEventListener("click", () => {
236
- apiModal.hidden = false;
237
- });
238
- closeApi.addEventListener("click", () => apiModal.hidden = true);
239
- copyCurl.addEventListener("click", () => {
240
- navigator.clipboard.writeText(curlExample.textContent).then(()=> showToast("Copied"));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  });
242
 
 
 
 
 
 
 
243
  // small helpers
244
  function showToast(text) {
245
- // lightweight toast
246
  const t = document.createElement("div");
247
  t.textContent = text;
248
  t.style.position = "fixed";
@@ -285,11 +338,10 @@
285
  currentSlug = arr[0].slug;
286
  currentUrl = `/f/${currentSlug}`;
287
  currentFile = null;
288
- // set preview to first history filename
289
  previewName.textContent = arr[0].filename;
290
  previewSize.textContent = humanFileSize(arr[0].size || 0);
291
- placeholder.hidden = true;
292
- preview.hidden = false;
293
  }
294
  })();
295
 
 
15
  const historyList = document.getElementById("historyList");
16
  const clearHistoryBtn = document.getElementById("clearHistory");
17
  const apiBtn = document.getElementById("apiBtn");
18
+ // NOTE: these might be null in some edge cases, so guard below
19
  const apiModal = document.getElementById("apiModal");
20
  const closeApi = document.getElementById("closeApi");
21
  const copyCurl = document.getElementById("copyCurl");
 
39
  }
40
 
41
  // show placeholder click to open file picker
42
+ if (placeholder && fileInput) {
43
+ placeholder.addEventListener("click", () => fileInput.click());
44
+ }
45
 
46
+ if (fileInput) {
47
+ fileInput.addEventListener("change", (ev) => {
48
+ const f = ev.target.files[0];
49
+ if (!f) return;
50
+ selectFile(f);
51
+ });
52
+ }
53
 
54
  function selectFile(file) {
55
  // client-side checks
 
67
  previewName.textContent = file.name;
68
  previewSize.textContent = humanFileSize(file.size);
69
  // preview thumb for images
70
+ if (file.type && file.type.startsWith("image/")) {
71
  const url = URL.createObjectURL(file);
72
  previewThumb.style.backgroundImage = `url(${url})`;
73
+ previewThumb.textContent = "";
74
  } else {
75
  previewThumb.style.backgroundImage = 'none';
76
+ previewThumb.textContent = ext ? ext.toUpperCase() : "";
77
  }
78
+ if (placeholder) placeholder.hidden = true;
79
+ if (preview) preview.hidden = false;
80
+ if (uploadProgress) uploadProgress.value = 0;
81
+ if (progressText) progressText.textContent = "";
82
  }
83
 
84
  async function uploadSelectedFile(customSlug = "") {
85
  if (!currentFile) {
86
  // trigger file picker
87
+ if (fileInput) fileInput.click();
88
  return;
89
  }
90
  const fd = new FormData();
 
97
  xhr.upload.onprogress = (e) => {
98
  if (e.lengthComputable) {
99
  const pct = Math.floor((e.loaded / e.total) * 100);
100
+ if (uploadProgress) uploadProgress.value = pct;
101
+ if (progressText) progressText.textContent = `${pct}%`;
102
  }
103
  };
104
 
 
126
  const err = tryParseJSON(xhr.responseText);
127
  alert((err && err.detail) ? err.detail : "Upload failed.");
128
  }
129
+ if (progressText) progressText.textContent = "";
130
+ if (uploadProgress) uploadProgress.value = 0;
131
  // keep preview visible and set currentUrl for view/download
132
  if (currentSlug) {
133
  currentUrl = `/f/${currentSlug}`;
 
142
  }
143
 
144
  // wire upload button
145
+ if (uploadBtn) {
146
+ uploadBtn.addEventListener("click", async (ev) => {
147
+ // Shift-click allows custom slug (legacy behavior)
148
+ const isShift = ev.shiftKey;
149
+ let custom = "";
150
+ if (isShift) {
151
+ custom = prompt("Enter custom slug (letters, numbers, -, _ )");
152
+ if (!custom) custom = "";
153
+ }
154
+ await uploadSelectedFile(custom);
155
+ });
156
+ }
157
 
158
+ if (copyBtn) {
159
+ copyBtn.addEventListener("click", () => {
160
+ if (!currentUrl && !currentSlug) return showToast("No link yet");
161
+ const u = window.location.origin + (currentUrl || `/f/${currentSlug}`);
162
+ navigator.clipboard.writeText(u).then(() => showToast("Copied"));
163
+ });
164
+ }
165
 
166
+ if (viewBtn) {
167
+ viewBtn.addEventListener("click", () => {
168
+ if (!currentUrl && !currentSlug) return showToast("No file to view");
169
+ const u = (currentUrl || `/f/${currentSlug}`);
170
+ window.open(u, "_blank");
171
+ });
172
+ }
173
 
174
+ if (dlBtn) {
175
+ dlBtn.addEventListener("click", () => {
176
+ if (!currentUrl && !currentSlug) return showToast("No file to download");
177
+ const u = (currentUrl || `/f/${currentSlug}`) + "?dl=1";
178
+ window.open(u, "_blank");
179
+ });
180
+ }
181
 
182
  // history in localStorage
183
  function historyKey(){ return "doto_history_v1" }
 
206
 
207
  function renderHistory(){
208
  const arr = getHistory();
209
+ if (!historyList) return;
210
  historyList.innerHTML = "";
211
  if (arr.length === 0) {
212
  historyList.innerHTML = "<div class='muted'>No recent uploads</div>";
 
215
  arr.forEach(item => {
216
  const el = document.createElement("div");
217
  el.className = "history-item";
218
+ // show inline thumbnail only for images (browser will try to load)
219
+ const thumbStyle = (item.filename && item.filename.match(/\.(jpg|jpeg|png|gif|webp)$/i))
220
+ ? `background-image:url(${window.location.origin}/f/${item.slug})` : "";
221
  el.innerHTML = `
222
+ <div class="history-thumb" style="${thumbStyle}"></div>
223
  <div class="history-meta">
224
  <div style="font-weight:700">${escapeHtml(item.filename || item.slug)}</div>
225
  <div style="font-size:12px;color:var(--muted)">${timeAgo(item.created_at)}</div>
 
233
  });
234
  }
235
 
236
+ if (historyList) {
237
+ historyList.addEventListener("click", (e) => {
238
+ const btn = e.target.closest("button");
239
+ if (!btn) return;
240
+ const slug = btn.dataset.slug;
241
+ const action = btn.dataset.action;
242
+ if (action === "copy") {
243
+ const u = window.location.origin + `/f/${slug}`;
244
+ navigator.clipboard.writeText(u).then(()=> showToast("Copied"));
245
+ } else if (action === "open") {
246
+ window.open(`/f/${slug}`, "_blank");
247
+ }
248
+ });
249
+ }
250
 
251
+ if (clearHistoryBtn) {
252
+ clearHistoryBtn.addEventListener("click", () => {
253
+ if (confirm("Clear local history?")) clearHistory();
254
+ });
255
+ }
256
 
257
+ // modal api - robust handlers
258
+ if (apiBtn && apiModal) {
259
+ apiBtn.addEventListener("click", () => {
260
+ apiModal.hidden = false;
261
+ // focus first interactive element in modal if any
262
+ const focusable = apiModal.querySelector("button, [tabindex]:not([tabindex='-1'])");
263
+ if (focusable) focusable.focus();
264
+ });
265
+ }
266
+
267
+ // close click handler (guarded)
268
+ if (closeApi && apiModal) {
269
+ closeApi.addEventListener("click", () => {
270
+ apiModal.hidden = true;
271
+ });
272
+ }
273
+
274
+ // click outside modal content (backdrop)
275
+ if (apiModal) {
276
+ apiModal.addEventListener("click", (e) => {
277
+ // if click directly on the backdrop (modal container) close it
278
+ if (e.target === apiModal) {
279
+ apiModal.hidden = true;
280
+ }
281
+ });
282
+ }
283
+
284
+ // close on Escape key
285
+ document.addEventListener("keydown", (e) => {
286
+ if (e.key === "Escape" && apiModal && !apiModal.hidden) {
287
+ apiModal.hidden = true;
288
+ }
289
  });
290
 
291
+ if (copyCurl && curlExample) {
292
+ copyCurl.addEventListener("click", () => {
293
+ navigator.clipboard.writeText(curlExample.textContent).then(()=> showToast("Copied"));
294
+ });
295
+ }
296
+
297
  // small helpers
298
  function showToast(text) {
 
299
  const t = document.createElement("div");
300
  t.textContent = text;
301
  t.style.position = "fixed";
 
338
  currentSlug = arr[0].slug;
339
  currentUrl = `/f/${currentSlug}`;
340
  currentFile = null;
 
341
  previewName.textContent = arr[0].filename;
342
  previewSize.textContent = humanFileSize(arr[0].size || 0);
343
+ if (placeholder) placeholder.hidden = true;
344
+ if (preview) preview.hidden = false;
345
  }
346
  })();
347