pandorga 1.4.5 → 1.4.7
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 +21 -0
- data/assets/css/ledger/home.css +21 -11
- data/functions/api/studio/_lib/github.js +37 -15
- data/lib/pandorga/version.rb +1 -1
- data/scripts/test/render-parity/blocks/project-specimen.json +5 -3
- data/scripts/test/render-parity/blocks/system-card.json +8 -3
- data/scripts/test/test-studio-api-contract.rb +70 -0
- data/scripts/test/test-studio-pipeline-config.mjs +35 -0
- data/scripts/test/test-studio-pipeline-focus.mjs +308 -0
- data/scripts/validate.sh +10 -0
- data/studio/assets/{index-OAmrbW7i.js → index-BJyN-kB3.js} +98 -98
- data/studio/index.html +1 -1
- data/studio-app/src/main.js +131 -38
- metadata +4 -2
data/studio/index.html
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
rel="stylesheet"
|
|
19
19
|
href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css"
|
|
20
20
|
/>
|
|
21
|
-
<script type="module" crossorigin src="/studio/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/studio/assets/index-BJyN-kB3.js"></script>
|
|
22
22
|
<link rel="stylesheet" crossorigin href="/studio/assets/index-Cd1mlY_H.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body>
|
data/studio-app/src/main.js
CHANGED
|
@@ -907,9 +907,20 @@ function pipelineChipMeta(pipelineState) {
|
|
|
907
907
|
}
|
|
908
908
|
}
|
|
909
909
|
|
|
910
|
+
function pipelineChipViewKey() {
|
|
911
|
+
const meta = pipelineChipMeta(state.pipeline.state);
|
|
912
|
+
return JSON.stringify([
|
|
913
|
+
meta.className,
|
|
914
|
+
meta.spinning,
|
|
915
|
+
meta.icon,
|
|
916
|
+
meta.label,
|
|
917
|
+
meta.title,
|
|
918
|
+
state.pipeline.runUrl || "",
|
|
919
|
+
]);
|
|
920
|
+
}
|
|
921
|
+
|
|
910
922
|
function renderPipelineChip() {
|
|
911
923
|
const meta = pipelineChipMeta(state.pipeline.state);
|
|
912
|
-
const runUrl = state.pipeline.runUrl;
|
|
913
924
|
const kids = [
|
|
914
925
|
el("span", {
|
|
915
926
|
className:
|
|
@@ -928,11 +939,13 @@ function renderPipelineChip() {
|
|
|
928
939
|
"aria-label": meta.label,
|
|
929
940
|
role: "status",
|
|
930
941
|
"data-studio-action": "pipeline-status",
|
|
931
|
-
|
|
942
|
+
"data-pipeline-key": pipelineChipViewKey(),
|
|
943
|
+
disabled: !state.pipeline.runUrl,
|
|
932
944
|
onClick: () => {
|
|
933
|
-
|
|
945
|
+
const url = state.pipeline.runUrl;
|
|
946
|
+
if (!url) return;
|
|
934
947
|
try {
|
|
935
|
-
window.open(
|
|
948
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
936
949
|
} catch {
|
|
937
950
|
/* ignore */
|
|
938
951
|
}
|
|
@@ -942,6 +955,18 @@ function renderPipelineChip() {
|
|
|
942
955
|
);
|
|
943
956
|
}
|
|
944
957
|
|
|
958
|
+
/**
|
|
959
|
+
* Swap the toolbar chip only. renderShell() wipes #app and remounts the
|
|
960
|
+
* editor, so a poll tick would steal focus for the whole pipeline run.
|
|
961
|
+
*/
|
|
962
|
+
function syncPipelineChip() {
|
|
963
|
+
const current = document.querySelector("[data-studio-action='pipeline-status']");
|
|
964
|
+
if (!current) return;
|
|
965
|
+
const key = pipelineChipViewKey();
|
|
966
|
+
if (current.getAttribute("data-pipeline-key") === key) return;
|
|
967
|
+
current.replaceWith(renderPipelineChip());
|
|
968
|
+
}
|
|
969
|
+
|
|
945
970
|
function applyPipelinePayload(data) {
|
|
946
971
|
state.pipeline.runUrl = data.run?.html_url || state.pipeline.runUrl || "";
|
|
947
972
|
if (data.sha) state.pipeline.sha = String(data.sha).toLowerCase();
|
|
@@ -949,12 +974,14 @@ function applyPipelinePayload(data) {
|
|
|
949
974
|
if (data.state === "unconfigured") {
|
|
950
975
|
state.pipeline.state = "unconfigured";
|
|
951
976
|
state.pipeline.message =
|
|
977
|
+
data.message ||
|
|
952
978
|
"Set pandorga.content.workflow in _config.yml (e.g. content-pipeline.yml)";
|
|
953
979
|
return;
|
|
954
980
|
}
|
|
955
981
|
state.pipeline.state = data.state || "idle";
|
|
956
982
|
if (data.state === "error") {
|
|
957
|
-
state.pipeline.message =
|
|
983
|
+
state.pipeline.message =
|
|
984
|
+
data.message || "Content pipeline failed for this commit.";
|
|
958
985
|
}
|
|
959
986
|
}
|
|
960
987
|
|
|
@@ -964,14 +991,11 @@ async function refreshPipelineStatus(sha = null) {
|
|
|
964
991
|
try {
|
|
965
992
|
const data = await studioApi.pipelineStatus(sha || null);
|
|
966
993
|
applyPipelinePayload(data);
|
|
967
|
-
|
|
994
|
+
syncPipelineChip();
|
|
968
995
|
} catch (err) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
state.pipeline.message = String(err.message || err);
|
|
973
|
-
renderShell();
|
|
974
|
-
}
|
|
996
|
+
state.pipeline.state = "error";
|
|
997
|
+
state.pipeline.message = String(err.message || err);
|
|
998
|
+
syncPipelineChip();
|
|
975
999
|
}
|
|
976
1000
|
}
|
|
977
1001
|
|
|
@@ -986,7 +1010,7 @@ function armPipelineWatch(commitSha) {
|
|
|
986
1010
|
state.pipeline.sha = sha;
|
|
987
1011
|
state.pipeline.runUrl = "";
|
|
988
1012
|
state.pipeline.message = "";
|
|
989
|
-
|
|
1013
|
+
syncPipelineChip();
|
|
990
1014
|
void pollPipelineStatus(gen, sha);
|
|
991
1015
|
}
|
|
992
1016
|
|
|
@@ -1002,7 +1026,7 @@ async function pollPipelineStatus(gen, sha) {
|
|
|
1002
1026
|
if (state.pipeline.state === "running" || state.pipeline.state === "awaiting_run") {
|
|
1003
1027
|
state.pipeline.state = "stalled";
|
|
1004
1028
|
state.pipeline.message = "Timed out waiting for the content pipeline.";
|
|
1005
|
-
|
|
1029
|
+
syncPipelineChip();
|
|
1006
1030
|
}
|
|
1007
1031
|
return;
|
|
1008
1032
|
}
|
|
@@ -1014,15 +1038,15 @@ async function pollPipelineStatus(gen, sha) {
|
|
|
1014
1038
|
applyPipelinePayload(data);
|
|
1015
1039
|
|
|
1016
1040
|
if (data.state === "unconfigured" || data.state === "ok" || data.state === "error") {
|
|
1017
|
-
|
|
1041
|
+
syncPipelineChip();
|
|
1018
1042
|
return;
|
|
1019
1043
|
}
|
|
1020
1044
|
if (data.state === "awaiting_run" && elapsed > STALL_MS) {
|
|
1021
1045
|
state.pipeline.state = "stalled";
|
|
1022
|
-
|
|
1046
|
+
syncPipelineChip();
|
|
1023
1047
|
return;
|
|
1024
1048
|
}
|
|
1025
|
-
|
|
1049
|
+
syncPipelineChip();
|
|
1026
1050
|
} catch (err) {
|
|
1027
1051
|
if (gen !== state.pipeline.pollGen) return;
|
|
1028
1052
|
softFails += 1;
|
|
@@ -1030,13 +1054,13 @@ async function pollPipelineStatus(gen, sha) {
|
|
|
1030
1054
|
if (code === "actions_forbidden" || code === "github_unconfigured") {
|
|
1031
1055
|
state.pipeline.state = "error";
|
|
1032
1056
|
state.pipeline.message = String(err.message || err);
|
|
1033
|
-
|
|
1057
|
+
syncPipelineChip();
|
|
1034
1058
|
return;
|
|
1035
1059
|
}
|
|
1036
1060
|
if (softFails >= 5) {
|
|
1037
1061
|
state.pipeline.state = "error";
|
|
1038
1062
|
state.pipeline.message = String(err.message || err);
|
|
1039
|
-
|
|
1063
|
+
syncPipelineChip();
|
|
1040
1064
|
return;
|
|
1041
1065
|
}
|
|
1042
1066
|
}
|
|
@@ -1118,6 +1142,61 @@ function toolbarActionButton({
|
|
|
1118
1142
|
);
|
|
1119
1143
|
}
|
|
1120
1144
|
|
|
1145
|
+
function localExportPresentation() {
|
|
1146
|
+
const exportBusy = state.reexporting || state.pipeline.state === "syncing";
|
|
1147
|
+
return {
|
|
1148
|
+
exportBusy,
|
|
1149
|
+
className:
|
|
1150
|
+
"btn btn-tool btn-reexport" +
|
|
1151
|
+
(exportBusy ? " pipeline-running" : "") +
|
|
1152
|
+
(state.pipeline.state === "ok" && !exportBusy ? " pipeline-ok" : "") +
|
|
1153
|
+
(state.pipeline.state === "error" && !exportBusy ? " pipeline-error" : ""),
|
|
1154
|
+
title: exportBusy
|
|
1155
|
+
? "Exporting content JSON locally"
|
|
1156
|
+
: "Export content JSON locally and reload",
|
|
1157
|
+
ariaLabel: exportBusy ? "Exporting locally" : "Export locally",
|
|
1158
|
+
icon: exportBusy ? "progress_activity" : "sync",
|
|
1159
|
+
label: exportBusy ? "Exporting…" : "Export",
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/** Patch the local Export button without remounting the editor. */
|
|
1164
|
+
function syncLocalExportButton() {
|
|
1165
|
+
const btn = document.querySelector("[data-studio-action='reexport']");
|
|
1166
|
+
if (!btn) return;
|
|
1167
|
+
const exp = localExportPresentation();
|
|
1168
|
+
btn.className = exp.className;
|
|
1169
|
+
btn.title = exp.title;
|
|
1170
|
+
btn.setAttribute("aria-label", exp.ariaLabel);
|
|
1171
|
+
btn.disabled = exp.exportBusy;
|
|
1172
|
+
const icon = btn.querySelector(".material-symbols-outlined");
|
|
1173
|
+
if (icon) {
|
|
1174
|
+
icon.textContent = exp.icon;
|
|
1175
|
+
icon.classList.toggle("studio-icon-spin", exp.exportBusy);
|
|
1176
|
+
}
|
|
1177
|
+
const label = btn.querySelector(".btn-label");
|
|
1178
|
+
if (label) label.textContent = exp.label;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function syncToolbarStatus() {
|
|
1182
|
+
const brand = document.querySelector(".studio-toolbar-brand");
|
|
1183
|
+
if (!brand) return;
|
|
1184
|
+
const statusText = state.error ? "Error" : state.status || "";
|
|
1185
|
+
let pill = brand.querySelector(".studio-toolbar-status");
|
|
1186
|
+
if (!statusText) {
|
|
1187
|
+
if (pill) pill.remove();
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (!pill) {
|
|
1191
|
+
pill = el("span", { className: "status-pill studio-toolbar-status" });
|
|
1192
|
+
brand.append(pill);
|
|
1193
|
+
}
|
|
1194
|
+
pill.className =
|
|
1195
|
+
"status-pill studio-toolbar-status" + (state.error ? " error" : "");
|
|
1196
|
+
pill.textContent = statusText;
|
|
1197
|
+
pill.title = state.error || state.errorDetail || state.status || "";
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1121
1200
|
function renderStudioToolbar({ brand = null, menuToggle = null } = {}) {
|
|
1122
1201
|
const saveLabel = state.saving ? "Saving…" : state.dirty ? "Save" : "Saved";
|
|
1123
1202
|
const saveIcon = state.saving ? "progress_activity" : state.dirty ? "save" : "check";
|
|
@@ -1138,24 +1217,18 @@ function renderStudioToolbar({ brand = null, menuToggle = null } = {}) {
|
|
|
1138
1217
|
|
|
1139
1218
|
// Theme → pipeline/Export → Revert → Save. Pipeline chip is always present.
|
|
1140
1219
|
if (isLocalDevHost()) {
|
|
1141
|
-
const
|
|
1220
|
+
const exp = localExportPresentation();
|
|
1142
1221
|
actions.push(
|
|
1143
1222
|
toolbarActionButton({
|
|
1144
|
-
className:
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
(state.pipeline.state === "ok" && !exportBusy ? " pipeline-ok" : "") +
|
|
1148
|
-
(state.pipeline.state === "error" && !exportBusy ? " pipeline-error" : ""),
|
|
1149
|
-
title: exportBusy
|
|
1150
|
-
? "Exporting content JSON locally"
|
|
1151
|
-
: "Export content JSON locally and reload",
|
|
1152
|
-
ariaLabel: exportBusy ? "Exporting locally" : "Export locally",
|
|
1223
|
+
className: exp.className,
|
|
1224
|
+
title: exp.title,
|
|
1225
|
+
ariaLabel: exp.ariaLabel,
|
|
1153
1226
|
action: "reexport",
|
|
1154
|
-
disabled: exportBusy,
|
|
1227
|
+
disabled: exp.exportBusy,
|
|
1155
1228
|
onClick: () => reexportLocalContent(),
|
|
1156
|
-
icon:
|
|
1157
|
-
label:
|
|
1158
|
-
spinning: exportBusy,
|
|
1229
|
+
icon: exp.icon,
|
|
1230
|
+
label: exp.label,
|
|
1231
|
+
spinning: exp.exportBusy,
|
|
1159
1232
|
})
|
|
1160
1233
|
);
|
|
1161
1234
|
} else {
|
|
@@ -1431,6 +1504,21 @@ function renderErrorBanner() {
|
|
|
1431
1504
|
]);
|
|
1432
1505
|
}
|
|
1433
1506
|
|
|
1507
|
+
/** Show or hide the failure banner without rebuilding the editor. */
|
|
1508
|
+
function syncErrorBanner() {
|
|
1509
|
+
const main = document.querySelector(".studio-main");
|
|
1510
|
+
if (!main) return;
|
|
1511
|
+
const existing = main.querySelector(":scope > .studio-error-banner");
|
|
1512
|
+
const next = renderErrorBanner();
|
|
1513
|
+
if (existing && next) existing.replaceWith(next);
|
|
1514
|
+
else if (existing && !next) existing.remove();
|
|
1515
|
+
else if (!existing && next) {
|
|
1516
|
+
const crumb = main.querySelector(".studio-breadcrumb");
|
|
1517
|
+
if (crumb) crumb.before(next);
|
|
1518
|
+
else main.prepend(next);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1434
1522
|
function renderShell() {
|
|
1435
1523
|
app.innerHTML = "";
|
|
1436
1524
|
const groups = collectionGroups(state.schema);
|
|
@@ -4009,7 +4097,9 @@ async function reexportLocalContent() {
|
|
|
4009
4097
|
state.pipeline.message = "";
|
|
4010
4098
|
state.status = "Reexporting…";
|
|
4011
4099
|
clearError();
|
|
4012
|
-
|
|
4100
|
+
syncToolbarStatus();
|
|
4101
|
+
syncErrorBanner();
|
|
4102
|
+
syncLocalExportButton();
|
|
4013
4103
|
try {
|
|
4014
4104
|
const res = await fetch("/__dev/reexport", {
|
|
4015
4105
|
method: "POST",
|
|
@@ -4024,9 +4114,11 @@ async function reexportLocalContent() {
|
|
|
4024
4114
|
if (!res.ok || !data.ok) {
|
|
4025
4115
|
throw new Error((data && data.error) || `Export failed: ${res.status}`);
|
|
4026
4116
|
}
|
|
4117
|
+
state.reexporting = false;
|
|
4027
4118
|
state.pipeline.state = "ok";
|
|
4028
4119
|
state.status = "Reexported — reloading…";
|
|
4029
|
-
|
|
4120
|
+
syncToolbarStatus();
|
|
4121
|
+
syncLocalExportButton();
|
|
4030
4122
|
setTimeout(() => {
|
|
4031
4123
|
try {
|
|
4032
4124
|
const url = new URL(window.location.href);
|
|
@@ -4037,12 +4129,13 @@ async function reexportLocalContent() {
|
|
|
4037
4129
|
}
|
|
4038
4130
|
}, 200);
|
|
4039
4131
|
} catch (err) {
|
|
4132
|
+
state.reexporting = false;
|
|
4040
4133
|
state.pipeline.state = "error";
|
|
4041
4134
|
state.pipeline.message = String(err.message || err);
|
|
4042
4135
|
captureError(err);
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4136
|
+
syncToolbarStatus();
|
|
4137
|
+
syncErrorBanner();
|
|
4138
|
+
syncLocalExportButton();
|
|
4046
4139
|
}
|
|
4047
4140
|
}
|
|
4048
4141
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pandorga
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The Pandorga contributors
|
|
@@ -412,6 +412,8 @@ files:
|
|
|
412
412
|
- scripts/test/test-studio-api-contract.rb
|
|
413
413
|
- scripts/test/test-studio-content-files.rb
|
|
414
414
|
- scripts/test/test-studio-liquid-roundtrip.mjs
|
|
415
|
+
- scripts/test/test-studio-pipeline-config.mjs
|
|
416
|
+
- scripts/test/test-studio-pipeline-focus.mjs
|
|
415
417
|
- scripts/test/test-studio-rate-limit.mjs
|
|
416
418
|
- scripts/test/test-studio-rewrite-api-contract.rb
|
|
417
419
|
- scripts/test/test-studio-rewrite-sanitize.mjs
|
|
@@ -449,8 +451,8 @@ files:
|
|
|
449
451
|
- studio-app/src/preview/render.js
|
|
450
452
|
- studio-app/src/styles/studio.css
|
|
451
453
|
- studio-app/vite.config.js
|
|
454
|
+
- studio/assets/index-BJyN-kB3.js
|
|
452
455
|
- studio/assets/index-Cd1mlY_H.css
|
|
453
|
-
- studio/assets/index-OAmrbW7i.js
|
|
454
456
|
- studio/index.html
|
|
455
457
|
- studio/schema.yml
|
|
456
458
|
- templates/articles/README.md
|