pandorga 1.3.11 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -203,11 +203,6 @@ export function renderMediaPane(opts) {
203
203
  fileInput,
204
204
  ]);
205
205
 
206
- const hint = el("p", {
207
- className: "media-hint",
208
- text: "Click an image, video, or HTML file to preview it. Copy the public path into includes (e.g. /media/images/…). Deletes stay in GitHub/IDE.",
209
- });
210
-
211
206
  let body;
212
207
  if (state.mediaLoading) {
213
208
  body = el("p", { className: "loading-msg", text: "Loading media…" });
@@ -218,14 +213,13 @@ export function renderMediaPane(opts) {
218
213
  });
219
214
  } else {
220
215
  const rows = items.map((item) => renderMediaRow(item, opts));
221
- body = el("div", { className: "media-grid", role: "list" }, rows);
216
+ body = el("div", { className: "media-grid" }, rows);
222
217
  }
223
218
 
224
219
  return el("div", { className: "pane media-pane" }, [
225
220
  el("header", { className: "media-header" }, [
226
221
  el("h1", { className: "media-title", text: "Media" }),
227
222
  breadcrumb,
228
- hint,
229
223
  ]),
230
224
  toolbar,
231
225
  body,
@@ -293,17 +287,35 @@ function renderMediaRow(item, opts) {
293
287
  ? `Preview ${item.name}`
294
288
  : `Copy ${publicPath}`;
295
289
 
296
- return el("div", {
297
- className: "media-row" + (isDir ? " media-row--dir" : ""),
298
- role: "listitem",
299
- }, [
300
- el("button", {
301
- className: "media-row-main" + (kind ? " media-row-main--preview" : ""),
302
- type: "button",
290
+ const ops = !isDir && publicPath
291
+ ? renderMediaOps(item, publicPath, opts)
292
+ : null;
293
+
294
+ return el(
295
+ "div",
296
+ {
297
+ className:
298
+ "media-row" +
299
+ (isDir ? " media-row--dir" : "") +
300
+ (kind ? " media-row--preview" : ""),
301
+ role: "button",
302
+ tabindex: state.mediaBusy ? "-1" : "0",
303
303
  title: rowTitle,
304
- disabled: !!state.mediaBusy,
305
- onClick: openOrCopy,
306
- }, [
304
+ "aria-disabled": state.mediaBusy ? "true" : "false",
305
+ onClick: (e) => {
306
+ if (state.mediaBusy) return;
307
+ if (e.target.closest(".media-ops")) return;
308
+ openOrCopy(e);
309
+ },
310
+ onKeydown: (e) => {
311
+ if (state.mediaBusy) return;
312
+ if (e.key === "Enter" || e.key === " ") {
313
+ e.preventDefault();
314
+ openOrCopy(e);
315
+ }
316
+ },
317
+ },
318
+ [
307
319
  preview,
308
320
  el("span", { className: "media-row-body" }, [
309
321
  el("span", { className: "media-row-name", text: item.name }),
@@ -312,18 +324,48 @@ function renderMediaRow(item, opts) {
312
324
  text: metaParts.join(" · ") || (isDir ? "Folder" : ""),
313
325
  }),
314
326
  ]),
315
- ]),
316
- !isDir && publicPath
317
- ? el("button", {
318
- className: "btn media-copy-btn",
319
- type: "button",
320
- text: "Copy path",
321
- title: publicPath,
322
- disabled: !!state.mediaBusy,
323
- onClick: () => void copyPath(publicPath, opts),
324
- })
325
- : null,
326
- ].filter(Boolean));
327
+ ops,
328
+ ].filter(Boolean)
329
+ );
330
+ }
331
+
332
+ function renderMediaOps(item, publicPath, opts) {
333
+ const { el, materialIcon, state } = opts;
334
+ const busy = !!state.mediaBusy;
335
+ const copyBtn = el(
336
+ "button",
337
+ {
338
+ className: "btn btn-icon media-op-btn media-copy-btn",
339
+ type: "button",
340
+ title: `Copy ${publicPath}`,
341
+ "aria-label": `Copy path ${publicPath}`,
342
+ disabled: busy,
343
+ onClick: (e) => {
344
+ e.stopPropagation();
345
+ void copyPath(publicPath, opts);
346
+ },
347
+ },
348
+ [materialIcon("content_copy")]
349
+ );
350
+ const deleteBtn = el(
351
+ "button",
352
+ {
353
+ className: "btn btn-icon media-op-btn media-delete-btn",
354
+ type: "button",
355
+ title: `Delete ${item.name}`,
356
+ "aria-label": `Delete ${item.name}`,
357
+ disabled: busy,
358
+ onClick: (e) => {
359
+ e.stopPropagation();
360
+ void deleteMediaFile(item, opts);
361
+ },
362
+ },
363
+ [materialIcon("delete")]
364
+ );
365
+ return el("div", { className: "media-ops", role: "group", "aria-label": "Operations" }, [
366
+ copyBtn,
367
+ deleteBtn,
368
+ ]);
327
369
  }
328
370
 
329
371
  /** One overlay for the page, same chrome as the public figure lightbox. */
@@ -480,6 +522,43 @@ async function copyPath(publicPath, opts) {
480
522
  render();
481
523
  }
482
524
 
525
+ async function deleteMediaFile(item, opts) {
526
+ const { api, state, render, captureError, clearError } = opts;
527
+ const path = item?.path;
528
+ if (!path || item.type === "dir") return;
529
+ const name = item.name || String(path).split("/").pop() || path;
530
+ if (
531
+ !window.confirm(
532
+ `Delete ${name}?\n\nThis commits a deletion to main and cannot be undone from Studio.`
533
+ )
534
+ ) {
535
+ return;
536
+ }
537
+ clearError();
538
+ state.mediaBusy = true;
539
+ state.status = `Deleting ${path}…`;
540
+ render();
541
+ try {
542
+ let sha = item.sha || null;
543
+ if (!sha) {
544
+ const data = await api.getFile(path);
545
+ sha = data.sha;
546
+ }
547
+ await api.deleteFile({
548
+ path,
549
+ sha,
550
+ message: `studio: delete ${path}`,
551
+ });
552
+ state.mediaBusy = false;
553
+ state.status = `Deleted ${path}`;
554
+ await openMediaDir(state.mediaPath, opts);
555
+ } catch (err) {
556
+ state.mediaBusy = false;
557
+ captureError(err);
558
+ render();
559
+ }
560
+ }
561
+
483
562
  export async function openMediaDir(dirPath, opts) {
484
563
  const { api, state, render, captureError, clearError } = opts;
485
564
  clearError();