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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +99 -0
- data/_includes/content-runtime/43-blocks.html +40 -16
- data/_layouts/home.html +5 -1
- data/assets/css/ledger/home.css +45 -1
- data/functions/api/studio/[[path]].js +27 -0
- data/functions/api/studio/_lib/github.js +23 -0
- data/lib/pandorga/version.rb +1 -1
- data/scripts/test/render-parity/blocks/project-inventory-row.json +10 -4
- data/scripts/test/render-parity/run-absent-field-checks.mjs +2 -1
- data/scripts/test/test-studio-slug-config.rb +21 -14
- data/studio/assets/index-BQdNT4zp.css +1 -0
- data/studio/assets/index-pL2vB_T0.js +295 -0
- data/studio/index.html +2 -2
- data/studio/schema.yml +7 -7
- data/studio-app/src/api/client.js +5 -0
- data/studio-app/src/lib/schema.js +1 -1
- data/studio-app/src/main.js +406 -177
- data/studio-app/src/media/browser.js +108 -29
- data/studio-app/src/styles/studio.css +478 -251
- data/templates/articles/studio.yml +1 -1
- data/templates/bibliography/studio.yml +1 -1
- data/templates/blog/studio.yml +1 -1
- data/templates/cv/studio.yml +2 -2
- data/templates/media/studio.yml +1 -1
- data/templates/portfolio/studio.yml +1 -1
- metadata +3 -3
- data/studio/assets/index-B2os0c1O.css +0 -1
- data/studio/assets/index-CaMlZpH1.js +0 -285
|
@@ -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"
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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:
|
|
305
|
-
onClick:
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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();
|