pandorga 1.4.6 → 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 +8 -0
- data/assets/css/ledger/home.css +21 -11
- 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-focus.mjs +308 -0
- data/scripts/validate.sh +5 -0
- data/studio/assets/{index-CMVzh8F1.js → index-BJyN-kB3.js} +98 -98
- data/studio/index.html +1 -1
- data/studio-app/src/main.js +126 -32
- metadata +3 -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();
|
|
@@ -966,11 +991,11 @@ async function refreshPipelineStatus(sha = null) {
|
|
|
966
991
|
try {
|
|
967
992
|
const data = await studioApi.pipelineStatus(sha || null);
|
|
968
993
|
applyPipelinePayload(data);
|
|
969
|
-
|
|
994
|
+
syncPipelineChip();
|
|
970
995
|
} catch (err) {
|
|
971
996
|
state.pipeline.state = "error";
|
|
972
997
|
state.pipeline.message = String(err.message || err);
|
|
973
|
-
|
|
998
|
+
syncPipelineChip();
|
|
974
999
|
}
|
|
975
1000
|
}
|
|
976
1001
|
|
|
@@ -985,7 +1010,7 @@ function armPipelineWatch(commitSha) {
|
|
|
985
1010
|
state.pipeline.sha = sha;
|
|
986
1011
|
state.pipeline.runUrl = "";
|
|
987
1012
|
state.pipeline.message = "";
|
|
988
|
-
|
|
1013
|
+
syncPipelineChip();
|
|
989
1014
|
void pollPipelineStatus(gen, sha);
|
|
990
1015
|
}
|
|
991
1016
|
|
|
@@ -1001,7 +1026,7 @@ async function pollPipelineStatus(gen, sha) {
|
|
|
1001
1026
|
if (state.pipeline.state === "running" || state.pipeline.state === "awaiting_run") {
|
|
1002
1027
|
state.pipeline.state = "stalled";
|
|
1003
1028
|
state.pipeline.message = "Timed out waiting for the content pipeline.";
|
|
1004
|
-
|
|
1029
|
+
syncPipelineChip();
|
|
1005
1030
|
}
|
|
1006
1031
|
return;
|
|
1007
1032
|
}
|
|
@@ -1013,15 +1038,15 @@ async function pollPipelineStatus(gen, sha) {
|
|
|
1013
1038
|
applyPipelinePayload(data);
|
|
1014
1039
|
|
|
1015
1040
|
if (data.state === "unconfigured" || data.state === "ok" || data.state === "error") {
|
|
1016
|
-
|
|
1041
|
+
syncPipelineChip();
|
|
1017
1042
|
return;
|
|
1018
1043
|
}
|
|
1019
1044
|
if (data.state === "awaiting_run" && elapsed > STALL_MS) {
|
|
1020
1045
|
state.pipeline.state = "stalled";
|
|
1021
|
-
|
|
1046
|
+
syncPipelineChip();
|
|
1022
1047
|
return;
|
|
1023
1048
|
}
|
|
1024
|
-
|
|
1049
|
+
syncPipelineChip();
|
|
1025
1050
|
} catch (err) {
|
|
1026
1051
|
if (gen !== state.pipeline.pollGen) return;
|
|
1027
1052
|
softFails += 1;
|
|
@@ -1029,13 +1054,13 @@ async function pollPipelineStatus(gen, sha) {
|
|
|
1029
1054
|
if (code === "actions_forbidden" || code === "github_unconfigured") {
|
|
1030
1055
|
state.pipeline.state = "error";
|
|
1031
1056
|
state.pipeline.message = String(err.message || err);
|
|
1032
|
-
|
|
1057
|
+
syncPipelineChip();
|
|
1033
1058
|
return;
|
|
1034
1059
|
}
|
|
1035
1060
|
if (softFails >= 5) {
|
|
1036
1061
|
state.pipeline.state = "error";
|
|
1037
1062
|
state.pipeline.message = String(err.message || err);
|
|
1038
|
-
|
|
1063
|
+
syncPipelineChip();
|
|
1039
1064
|
return;
|
|
1040
1065
|
}
|
|
1041
1066
|
}
|
|
@@ -1117,6 +1142,61 @@ function toolbarActionButton({
|
|
|
1117
1142
|
);
|
|
1118
1143
|
}
|
|
1119
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
|
+
|
|
1120
1200
|
function renderStudioToolbar({ brand = null, menuToggle = null } = {}) {
|
|
1121
1201
|
const saveLabel = state.saving ? "Saving…" : state.dirty ? "Save" : "Saved";
|
|
1122
1202
|
const saveIcon = state.saving ? "progress_activity" : state.dirty ? "save" : "check";
|
|
@@ -1137,24 +1217,18 @@ function renderStudioToolbar({ brand = null, menuToggle = null } = {}) {
|
|
|
1137
1217
|
|
|
1138
1218
|
// Theme → pipeline/Export → Revert → Save. Pipeline chip is always present.
|
|
1139
1219
|
if (isLocalDevHost()) {
|
|
1140
|
-
const
|
|
1220
|
+
const exp = localExportPresentation();
|
|
1141
1221
|
actions.push(
|
|
1142
1222
|
toolbarActionButton({
|
|
1143
|
-
className:
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
(state.pipeline.state === "ok" && !exportBusy ? " pipeline-ok" : "") +
|
|
1147
|
-
(state.pipeline.state === "error" && !exportBusy ? " pipeline-error" : ""),
|
|
1148
|
-
title: exportBusy
|
|
1149
|
-
? "Exporting content JSON locally"
|
|
1150
|
-
: "Export content JSON locally and reload",
|
|
1151
|
-
ariaLabel: exportBusy ? "Exporting locally" : "Export locally",
|
|
1223
|
+
className: exp.className,
|
|
1224
|
+
title: exp.title,
|
|
1225
|
+
ariaLabel: exp.ariaLabel,
|
|
1152
1226
|
action: "reexport",
|
|
1153
|
-
disabled: exportBusy,
|
|
1227
|
+
disabled: exp.exportBusy,
|
|
1154
1228
|
onClick: () => reexportLocalContent(),
|
|
1155
|
-
icon:
|
|
1156
|
-
label:
|
|
1157
|
-
spinning: exportBusy,
|
|
1229
|
+
icon: exp.icon,
|
|
1230
|
+
label: exp.label,
|
|
1231
|
+
spinning: exp.exportBusy,
|
|
1158
1232
|
})
|
|
1159
1233
|
);
|
|
1160
1234
|
} else {
|
|
@@ -1430,6 +1504,21 @@ function renderErrorBanner() {
|
|
|
1430
1504
|
]);
|
|
1431
1505
|
}
|
|
1432
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
|
+
|
|
1433
1522
|
function renderShell() {
|
|
1434
1523
|
app.innerHTML = "";
|
|
1435
1524
|
const groups = collectionGroups(state.schema);
|
|
@@ -4008,7 +4097,9 @@ async function reexportLocalContent() {
|
|
|
4008
4097
|
state.pipeline.message = "";
|
|
4009
4098
|
state.status = "Reexporting…";
|
|
4010
4099
|
clearError();
|
|
4011
|
-
|
|
4100
|
+
syncToolbarStatus();
|
|
4101
|
+
syncErrorBanner();
|
|
4102
|
+
syncLocalExportButton();
|
|
4012
4103
|
try {
|
|
4013
4104
|
const res = await fetch("/__dev/reexport", {
|
|
4014
4105
|
method: "POST",
|
|
@@ -4023,9 +4114,11 @@ async function reexportLocalContent() {
|
|
|
4023
4114
|
if (!res.ok || !data.ok) {
|
|
4024
4115
|
throw new Error((data && data.error) || `Export failed: ${res.status}`);
|
|
4025
4116
|
}
|
|
4117
|
+
state.reexporting = false;
|
|
4026
4118
|
state.pipeline.state = "ok";
|
|
4027
4119
|
state.status = "Reexported — reloading…";
|
|
4028
|
-
|
|
4120
|
+
syncToolbarStatus();
|
|
4121
|
+
syncLocalExportButton();
|
|
4029
4122
|
setTimeout(() => {
|
|
4030
4123
|
try {
|
|
4031
4124
|
const url = new URL(window.location.href);
|
|
@@ -4036,12 +4129,13 @@ async function reexportLocalContent() {
|
|
|
4036
4129
|
}
|
|
4037
4130
|
}, 200);
|
|
4038
4131
|
} catch (err) {
|
|
4132
|
+
state.reexporting = false;
|
|
4039
4133
|
state.pipeline.state = "error";
|
|
4040
4134
|
state.pipeline.message = String(err.message || err);
|
|
4041
4135
|
captureError(err);
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4136
|
+
syncToolbarStatus();
|
|
4137
|
+
syncErrorBanner();
|
|
4138
|
+
syncLocalExportButton();
|
|
4045
4139
|
}
|
|
4046
4140
|
}
|
|
4047
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
|
|
@@ -413,6 +413,7 @@ files:
|
|
|
413
413
|
- scripts/test/test-studio-content-files.rb
|
|
414
414
|
- scripts/test/test-studio-liquid-roundtrip.mjs
|
|
415
415
|
- scripts/test/test-studio-pipeline-config.mjs
|
|
416
|
+
- scripts/test/test-studio-pipeline-focus.mjs
|
|
416
417
|
- scripts/test/test-studio-rate-limit.mjs
|
|
417
418
|
- scripts/test/test-studio-rewrite-api-contract.rb
|
|
418
419
|
- scripts/test/test-studio-rewrite-sanitize.mjs
|
|
@@ -450,7 +451,7 @@ files:
|
|
|
450
451
|
- studio-app/src/preview/render.js
|
|
451
452
|
- studio-app/src/styles/studio.css
|
|
452
453
|
- studio-app/vite.config.js
|
|
453
|
-
- studio/assets/index-
|
|
454
|
+
- studio/assets/index-BJyN-kB3.js
|
|
454
455
|
- studio/assets/index-Cd1mlY_H.css
|
|
455
456
|
- studio/index.html
|
|
456
457
|
- studio/schema.yml
|