pressship 0.1.13 → 0.1.15
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.
- package/assets/web/app.js +2234 -212
- package/assets/web/style.css +2951 -988
- package/dist/checks/plugin-check.d.ts +1 -0
- package/dist/checks/plugin-check.js +72 -1
- package/dist/checks/plugin-check.js.map +1 -1
- package/dist/package/archive.d.ts +12 -0
- package/dist/package/archive.js +44 -6
- package/dist/package/archive.js.map +1 -1
- package/dist/package/ignore.d.ts +18 -0
- package/dist/package/ignore.js +156 -8
- package/dist/package/ignore.js.map +1 -1
- package/dist/svn/release.js +105 -8
- package/dist/svn/release.js.map +1 -1
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +76 -0
- package/dist/ui.js.map +1 -1
- package/dist/web/index.js +1 -0
- package/dist/web/index.js.map +1 -1
- package/dist/web/release.js +10 -6
- package/dist/web/release.js.map +1 -1
- package/dist/web/server.d.ts +7 -0
- package/dist/web/server.js +502 -67
- package/dist/web/server.js.map +1 -1
- package/dist/web/version-state.js +14 -10
- package/dist/web/version-state.js.map +1 -1
- package/dist/wordpress-org/submit.js +4 -2
- package/dist/wordpress-org/submit.js.map +1 -1
- package/package.json +1 -1
package/assets/web/app.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
let token = document.querySelector('meta[name="pressship-token"]').content;
|
|
4
4
|
|
|
5
5
|
let markdownParser = basicMarkdownToHtml;
|
|
6
|
+
let studioPackageSizePollTimer = null;
|
|
7
|
+
let monacoConfigured = false;
|
|
8
|
+
|
|
9
|
+
const STUDIO_CLI_PREFIX = "npx pressship";
|
|
6
10
|
|
|
7
11
|
void import("/vendor/marked.esm.js")
|
|
8
12
|
.then(({ marked }) => {
|
|
@@ -11,9 +15,11 @@ void import("/vendor/marked.esm.js")
|
|
|
11
15
|
breaks: true
|
|
12
16
|
});
|
|
13
17
|
markdownParser = (markdown) => marked.parse(markdown, { async: false });
|
|
18
|
+
refreshStudioAiMarkdownIfReady();
|
|
14
19
|
})
|
|
15
20
|
.catch(() => {
|
|
16
21
|
markdownParser = basicMarkdownToHtml;
|
|
22
|
+
refreshStudioAiMarkdownIfReady();
|
|
17
23
|
});
|
|
18
24
|
|
|
19
25
|
const MARKDOWN_ALLOWED_TAGS = new Set([
|
|
@@ -120,6 +126,8 @@ function clampStudioLayoutValue(key, value) {
|
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
const STUDIO_SIDEBAR_TAB_KEY = "pressship.studio.sidebar.tab.v1";
|
|
129
|
+
const STUDIO_PANEL_STORAGE_KEY = "pressship.studio.panels.v1";
|
|
130
|
+
const STUDIO_THEME_STORAGE_KEY = "pressship.studio.theme.v1";
|
|
123
131
|
|
|
124
132
|
function loadStudioSidebarTab(pluginKey) {
|
|
125
133
|
if (!pluginKey) {
|
|
@@ -147,6 +155,60 @@ function saveStudioSidebarTab(pluginKey, tab) {
|
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
|
|
158
|
+
function loadStudioPanelState() {
|
|
159
|
+
try {
|
|
160
|
+
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(STUDIO_PANEL_STORAGE_KEY) : null;
|
|
161
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
162
|
+
return normalizeStudioPanelState(parsed);
|
|
163
|
+
} catch {
|
|
164
|
+
return normalizeStudioPanelState();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeStudioPanelState(value = {}) {
|
|
169
|
+
return {
|
|
170
|
+
files: value.files !== false,
|
|
171
|
+
sidebar: value.sidebar !== false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function saveStudioPanelState(panels) {
|
|
176
|
+
try {
|
|
177
|
+
localStorage.setItem(STUDIO_PANEL_STORAGE_KEY, JSON.stringify(normalizeStudioPanelState(panels)));
|
|
178
|
+
} catch {
|
|
179
|
+
// ignore
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function normalizeStudioTheme(value) {
|
|
184
|
+
return value === "light" ? "light" : "dark";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function loadStudioTheme() {
|
|
188
|
+
try {
|
|
189
|
+
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(STUDIO_THEME_STORAGE_KEY) : null;
|
|
190
|
+
return normalizeStudioTheme(raw);
|
|
191
|
+
} catch {
|
|
192
|
+
return "dark";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function saveStudioTheme(theme) {
|
|
197
|
+
try {
|
|
198
|
+
localStorage.setItem(STUDIO_THEME_STORAGE_KEY, normalizeStudioTheme(theme));
|
|
199
|
+
} catch {
|
|
200
|
+
// ignore
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function studioTheme() {
|
|
205
|
+
return normalizeStudioTheme(state.studio.theme);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function studioMonacoTheme() {
|
|
209
|
+
return studioTheme() === "light" ? "pressship-studio-light" : "pressship-studio-dark";
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
function createInitialStudioRelease() {
|
|
151
213
|
return {
|
|
152
214
|
tags: null,
|
|
@@ -169,7 +231,31 @@ function createInitialStudioRelease() {
|
|
|
169
231
|
switchingResolution: "",
|
|
170
232
|
switchJobId: null,
|
|
171
233
|
switchConflict: null,
|
|
172
|
-
switchError: ""
|
|
234
|
+
switchError: "",
|
|
235
|
+
ignoredCollapsed: true
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function createInitialStudioIgnoreState() {
|
|
240
|
+
return {
|
|
241
|
+
ignorePath: "",
|
|
242
|
+
patterns: [],
|
|
243
|
+
ignoredFiles: []
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function createInitialStudioPackageSize() {
|
|
248
|
+
return {
|
|
249
|
+
loading: false,
|
|
250
|
+
error: "",
|
|
251
|
+
sizeBytes: null,
|
|
252
|
+
maxSizeBytes: 10 * 1024 * 1024,
|
|
253
|
+
overLimit: false,
|
|
254
|
+
fileCount: 0,
|
|
255
|
+
topLevelFolder: "",
|
|
256
|
+
largestFiles: [],
|
|
257
|
+
calculatedAt: null,
|
|
258
|
+
stale: false
|
|
173
259
|
};
|
|
174
260
|
}
|
|
175
261
|
|
|
@@ -342,6 +428,7 @@ const state = {
|
|
|
342
428
|
id: null,
|
|
343
429
|
plugin: null,
|
|
344
430
|
files: [],
|
|
431
|
+
directories: [],
|
|
345
432
|
selectedFile: null,
|
|
346
433
|
fileContent: "",
|
|
347
434
|
draftContent: "",
|
|
@@ -359,8 +446,11 @@ const state = {
|
|
|
359
446
|
playgroundUrl: "",
|
|
360
447
|
playgroundUrls: null,
|
|
361
448
|
activeTab: "editor",
|
|
449
|
+
openFiles: [],
|
|
362
450
|
terminalOpen: true,
|
|
363
451
|
collapsedFolders: new Set(),
|
|
452
|
+
expandedIgnoredFolders: new Set(),
|
|
453
|
+
loadingFolders: new Set(),
|
|
364
454
|
terminal: [],
|
|
365
455
|
aiPrompt: "",
|
|
366
456
|
aiJobId: null,
|
|
@@ -374,9 +464,17 @@ const state = {
|
|
|
374
464
|
editorKind: null,
|
|
375
465
|
editorModels: [],
|
|
376
466
|
layout: loadStudioLayout(),
|
|
467
|
+
panels: loadStudioPanelState(),
|
|
468
|
+
theme: loadStudioTheme(),
|
|
377
469
|
sidebarTab: "ai",
|
|
378
470
|
playgroundVersionModal: null,
|
|
379
471
|
release: createInitialStudioRelease(),
|
|
472
|
+
ignoreState: createInitialStudioIgnoreState(),
|
|
473
|
+
packageSize: createInitialStudioPackageSize(),
|
|
474
|
+
ignoreLoading: false,
|
|
475
|
+
ignoreError: "",
|
|
476
|
+
ignoreBusyPattern: "",
|
|
477
|
+
ignoreBusyPath: "",
|
|
380
478
|
pendingConfirms: new Map()
|
|
381
479
|
},
|
|
382
480
|
activeView: "dashboard",
|
|
@@ -423,6 +521,30 @@ const els = {
|
|
|
423
521
|
const isMac =
|
|
424
522
|
typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/i.test(navigator.platform || "");
|
|
425
523
|
let monacoPromise = null;
|
|
524
|
+
const VIEW_ROUTE_PATHS = {
|
|
525
|
+
dashboard: "/dashboard",
|
|
526
|
+
studio: "/studio",
|
|
527
|
+
remote: "/wordpress.org",
|
|
528
|
+
local: "/local",
|
|
529
|
+
release: "/release",
|
|
530
|
+
settings: "/settings"
|
|
531
|
+
};
|
|
532
|
+
const ROUTE_VIEW_ALIASES = {
|
|
533
|
+
"": "dashboard",
|
|
534
|
+
dashboard: "dashboard",
|
|
535
|
+
studio: "studio",
|
|
536
|
+
"wordpress.org": "remote",
|
|
537
|
+
remote: "remote",
|
|
538
|
+
local: "local",
|
|
539
|
+
release: "release",
|
|
540
|
+
settings: "settings"
|
|
541
|
+
};
|
|
542
|
+
let applyingLocationRoute = false;
|
|
543
|
+
let initialRouteLoaderVisible = false;
|
|
544
|
+
let studioFileSyncInFlight = false;
|
|
545
|
+
let studioLastExternalSyncAt = 0;
|
|
546
|
+
let studioLastDiskConflictKey = "";
|
|
547
|
+
|
|
426
548
|
document.querySelectorAll("[data-kbd-mod]").forEach((node) => {
|
|
427
549
|
node.textContent = isMac ? "⌘" : "Ctrl+";
|
|
428
550
|
});
|
|
@@ -482,6 +604,14 @@ document.addEventListener("keydown", (event) => {
|
|
|
482
604
|
return;
|
|
483
605
|
}
|
|
484
606
|
|
|
607
|
+
if (mod && event.key.toLowerCase() === "s" && !event.shiftKey && !event.altKey && state.activeView === "studio") {
|
|
608
|
+
event.preventDefault();
|
|
609
|
+
if (canSaveStudioFile()) {
|
|
610
|
+
void saveStudioFile();
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
485
615
|
if (event.key === "Escape" && state.command.open) {
|
|
486
616
|
event.preventDefault();
|
|
487
617
|
closeCommandPalette();
|
|
@@ -525,6 +655,258 @@ els.commandInput?.addEventListener("input", () => {
|
|
|
525
655
|
renderCommandPalette();
|
|
526
656
|
});
|
|
527
657
|
|
|
658
|
+
window.addEventListener("popstate", () => {
|
|
659
|
+
void applyLocationRoute({ replaceRoute: true });
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
window.addEventListener("focus", () => {
|
|
663
|
+
syncSelectedStudioFileOnResume();
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
document.addEventListener("visibilitychange", () => {
|
|
667
|
+
if (!document.hidden) {
|
|
668
|
+
syncSelectedStudioFileOnResume();
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
function normalizeViewId(view) {
|
|
673
|
+
return Object.prototype.hasOwnProperty.call(VIEW_ROUTE_PATHS, view) ? view : "dashboard";
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function normalizeBrowserPath(pathname = window.location.pathname) {
|
|
677
|
+
const trimmed = pathname.replace(/\/+$/, "");
|
|
678
|
+
return trimmed || "/";
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function shouldShowInitialRouteLoader() {
|
|
682
|
+
const path = normalizeBrowserPath();
|
|
683
|
+
return path !== "/" && path !== VIEW_ROUTE_PATHS.dashboard;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function showInitialRouteLoader() {
|
|
687
|
+
if (!shouldShowInitialRouteLoader() || initialRouteLoaderVisible) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
initialRouteLoaderVisible = true;
|
|
691
|
+
document.body.classList.add("is-initial-route-loading");
|
|
692
|
+
const loader = document.createElement("div");
|
|
693
|
+
loader.id = "initial-route-loader";
|
|
694
|
+
loader.className = "ps-initial-route-loader";
|
|
695
|
+
loader.setAttribute("role", "status");
|
|
696
|
+
loader.setAttribute("aria-live", "polite");
|
|
697
|
+
loader.innerHTML = `
|
|
698
|
+
<div class="ps-initial-route-loader-card">
|
|
699
|
+
<img src="/brand/pressship-symbol.png" alt="" />
|
|
700
|
+
<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
701
|
+
<strong>Loading Pressship Studio</strong>
|
|
702
|
+
<p>Restoring the requested page...</p>
|
|
703
|
+
</div>
|
|
704
|
+
`;
|
|
705
|
+
document.body.append(loader);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function hideInitialRouteLoader() {
|
|
709
|
+
if (!initialRouteLoaderVisible) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
initialRouteLoaderVisible = false;
|
|
713
|
+
document.body.classList.remove("is-initial-route-loading");
|
|
714
|
+
document.getElementById("initial-route-loader")?.remove();
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function decodeRouteSegment(segment) {
|
|
718
|
+
try {
|
|
719
|
+
return decodeURIComponent(segment);
|
|
720
|
+
} catch {
|
|
721
|
+
return segment;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function encodeRouteSegments(value) {
|
|
726
|
+
return String(value ?? "")
|
|
727
|
+
.split("/")
|
|
728
|
+
.filter(Boolean)
|
|
729
|
+
.map((segment) => encodeURIComponent(segment))
|
|
730
|
+
.join("/");
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function parseLocationRoute(pathname = window.location.pathname) {
|
|
734
|
+
const segments = pathname
|
|
735
|
+
.split("/")
|
|
736
|
+
.filter(Boolean)
|
|
737
|
+
.map((segment) => decodeRouteSegment(segment));
|
|
738
|
+
const head = segments[0] ?? "";
|
|
739
|
+
|
|
740
|
+
if (head === "studio") {
|
|
741
|
+
return {
|
|
742
|
+
view: "studio",
|
|
743
|
+
studio: {
|
|
744
|
+
project: segments[1] ?? "",
|
|
745
|
+
filePath: segments.slice(2).join("/")
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return {
|
|
751
|
+
view: ROUTE_VIEW_ALIASES[head] ?? "dashboard"
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function applyActiveViewShell(view) {
|
|
756
|
+
const nextView = normalizeViewId(view);
|
|
757
|
+
state.activeView = nextView;
|
|
758
|
+
document.body.dataset.activeView = nextView;
|
|
759
|
+
document.querySelectorAll(".view").forEach((node) => node.classList.remove("is-active"));
|
|
760
|
+
document.getElementById(`view-${nextView}`)?.classList.add("is-active");
|
|
761
|
+
document
|
|
762
|
+
.querySelectorAll("#adminmenu li")
|
|
763
|
+
.forEach((node) => node.classList.remove("wp-has-current-submenu"));
|
|
764
|
+
document
|
|
765
|
+
.querySelector(`#adminmenu li[data-view="${nextView}"]`)
|
|
766
|
+
?.classList.add("wp-has-current-submenu");
|
|
767
|
+
return nextView;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function primeInitialRouteState(route) {
|
|
771
|
+
const nextView = applyActiveViewShell(route.view);
|
|
772
|
+
if (nextView !== "studio" || !route.studio?.project || state.studio.id) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const filePath = route.studio.filePath;
|
|
777
|
+
state.studio = {
|
|
778
|
+
...state.studio,
|
|
779
|
+
scope: null,
|
|
780
|
+
id: route.studio.project,
|
|
781
|
+
plugin: { slug: route.studio.project, name: route.studio.project },
|
|
782
|
+
files: [],
|
|
783
|
+
directories: [],
|
|
784
|
+
selectedFile: filePath
|
|
785
|
+
? {
|
|
786
|
+
path: filePath,
|
|
787
|
+
name: filePath.split("/").pop() ?? filePath,
|
|
788
|
+
directory: filePath.includes("/") ? filePath.split("/").slice(0, -1).join("/") : "",
|
|
789
|
+
size: 0
|
|
790
|
+
}
|
|
791
|
+
: null,
|
|
792
|
+
fileContent: "",
|
|
793
|
+
draftContent: "",
|
|
794
|
+
readOnly: true,
|
|
795
|
+
dirty: false,
|
|
796
|
+
loading: true,
|
|
797
|
+
running: false,
|
|
798
|
+
checking: false,
|
|
799
|
+
jobId: null,
|
|
800
|
+
checkJobId: null,
|
|
801
|
+
activeTab: "editor",
|
|
802
|
+
openFiles: filePath ? [filePath] : [],
|
|
803
|
+
terminal: [
|
|
804
|
+
`Opening ${route.studio.project} from URL...`,
|
|
805
|
+
{ message: `$ ${studioOpenCliCommand()}`, tone: "command" }
|
|
806
|
+
],
|
|
807
|
+
collapsedFolders: new Set(),
|
|
808
|
+
expandedIgnoredFolders: new Set(),
|
|
809
|
+
loadingFolders: new Set(),
|
|
810
|
+
pendingConfirms: new Map()
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function studioProjectRouteSegment() {
|
|
815
|
+
const plugin = state.studio.plugin;
|
|
816
|
+
const localPlugin = state.local.find((item) => item.id === state.studio.id);
|
|
817
|
+
return plugin?.slug || localPlugin?.slug || plugin?.name || state.studio.id || "";
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function studioRoutePathForState() {
|
|
821
|
+
if (!state.studio.id) {
|
|
822
|
+
return VIEW_ROUTE_PATHS.studio;
|
|
823
|
+
}
|
|
824
|
+
const project = studioProjectRouteSegment();
|
|
825
|
+
if (!project) {
|
|
826
|
+
return VIEW_ROUTE_PATHS.studio;
|
|
827
|
+
}
|
|
828
|
+
const filePath =
|
|
829
|
+
state.studio.activeTab === "editor" && state.studio.selectedFile?.path
|
|
830
|
+
? encodeRouteSegments(state.studio.selectedFile.path)
|
|
831
|
+
: "";
|
|
832
|
+
const projectPath = encodeURIComponent(project);
|
|
833
|
+
return filePath ? `/studio/${projectPath}/${filePath}` : `/studio/${projectPath}`;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function routePathForState() {
|
|
837
|
+
const view = normalizeViewId(state.activeView);
|
|
838
|
+
return view === "studio" ? studioRoutePathForState() : VIEW_ROUTE_PATHS[view];
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function updateRouteFromState(options = {}) {
|
|
842
|
+
if (applyingLocationRoute && !options.force) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const nextPath = routePathForState();
|
|
846
|
+
if (normalizeBrowserPath(nextPath) === normalizeBrowserPath()) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const method = options.replace ? "replaceState" : "pushState";
|
|
850
|
+
history[method]({ pressshipView: state.activeView }, "", nextPath);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function resolveStudioRouteProject(project) {
|
|
854
|
+
if (!project) {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
const normalizedProject = project.toLowerCase();
|
|
858
|
+
const matchesProject = (candidate) => {
|
|
859
|
+
if (!candidate) {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
return candidate === project || candidate.toLowerCase() === normalizedProject;
|
|
863
|
+
};
|
|
864
|
+
const localPlugin = state.local.find((plugin) =>
|
|
865
|
+
[plugin.slug, plugin.id, plugin.name].some((candidate) => matchesProject(candidate))
|
|
866
|
+
);
|
|
867
|
+
if (localPlugin) {
|
|
868
|
+
return { scope: "local", id: localPlugin.id };
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const remotePlugin = state.remote.find((plugin) =>
|
|
872
|
+
[plugin.slug, plugin.name].some((candidate) => matchesProject(candidate))
|
|
873
|
+
);
|
|
874
|
+
return { scope: "remote", id: remotePlugin?.slug || project };
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async function applyLocationRoute(options = {}) {
|
|
878
|
+
const route = parseLocationRoute();
|
|
879
|
+
applyingLocationRoute = true;
|
|
880
|
+
try {
|
|
881
|
+
if (route.view === "studio") {
|
|
882
|
+
if (route.studio?.project) {
|
|
883
|
+
const target = resolveStudioRouteProject(route.studio.project);
|
|
884
|
+
if (target) {
|
|
885
|
+
await openStudio(target.scope, target.id, {
|
|
886
|
+
filePath: route.studio.filePath,
|
|
887
|
+
updateRoute: false
|
|
888
|
+
});
|
|
889
|
+
} else {
|
|
890
|
+
await showView("studio", { updateRoute: false });
|
|
891
|
+
renderStudio();
|
|
892
|
+
}
|
|
893
|
+
} else {
|
|
894
|
+
await showView("studio", { updateRoute: false });
|
|
895
|
+
renderStudio();
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
await showView(route.view, { updateRoute: false });
|
|
899
|
+
}
|
|
900
|
+
} finally {
|
|
901
|
+
applyingLocationRoute = false;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (options.replaceRoute !== false) {
|
|
905
|
+
updateRouteFromState({ replace: true, force: true });
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
showInitialRouteLoader();
|
|
528
910
|
void boot();
|
|
529
911
|
|
|
530
912
|
async function boot() {
|
|
@@ -532,13 +914,15 @@ async function boot() {
|
|
|
532
914
|
state.bootstrap = await api("/api/bootstrap");
|
|
533
915
|
refreshTokenFromBootstrap(state.bootstrap);
|
|
534
916
|
} catch (error) {
|
|
917
|
+
hideInitialRouteLoader();
|
|
535
918
|
notice(`Could not load bootstrap state: ${error.message}`, "error");
|
|
536
919
|
return;
|
|
537
920
|
}
|
|
538
921
|
state.settings = state.bootstrap.settings ?? null;
|
|
539
922
|
state.playgrounds = state.bootstrap.playgrounds ?? [];
|
|
540
923
|
state.aiAssistance.harnesses = state.bootstrap.aiHarnesses ?? [];
|
|
541
|
-
|
|
924
|
+
const initialRoute = parseLocationRoute();
|
|
925
|
+
primeInitialRouteState(initialRoute);
|
|
542
926
|
renderAccount();
|
|
543
927
|
for (const job of state.bootstrap.jobs ?? []) {
|
|
544
928
|
upsertJob(job);
|
|
@@ -549,8 +933,16 @@ async function boot() {
|
|
|
549
933
|
renderDashboard();
|
|
550
934
|
renderStudio();
|
|
551
935
|
renderPlaygroundsMenu();
|
|
936
|
+
if (state.activeView === "release") {
|
|
937
|
+
void loadReleaseBoard();
|
|
938
|
+
}
|
|
552
939
|
void loadAiAssistance();
|
|
553
|
-
|
|
940
|
+
try {
|
|
941
|
+
await Promise.all([loadRemote(), loadLocal()]);
|
|
942
|
+
await applyLocationRoute({ replaceRoute: true });
|
|
943
|
+
} finally {
|
|
944
|
+
hideInitialRouteLoader();
|
|
945
|
+
}
|
|
554
946
|
}
|
|
555
947
|
|
|
556
948
|
function renderAccount() {
|
|
@@ -598,14 +990,48 @@ async function runAction(name, element) {
|
|
|
598
990
|
await selectStudioFile(element.dataset.path);
|
|
599
991
|
return;
|
|
600
992
|
|
|
993
|
+
case "studio-ignore-file":
|
|
994
|
+
await addStudioIgnoreRule(studioFileIgnorePattern(element.dataset.path), {
|
|
995
|
+
path: element.dataset.path,
|
|
996
|
+
label: element.dataset.path
|
|
997
|
+
});
|
|
998
|
+
return;
|
|
999
|
+
|
|
1000
|
+
case "studio-ignore-folder":
|
|
1001
|
+
await addStudioIgnoreRule(studioFolderIgnorePattern(element.dataset.path), {
|
|
1002
|
+
path: element.dataset.path,
|
|
1003
|
+
label: `${element.dataset.path}/`
|
|
1004
|
+
});
|
|
1005
|
+
return;
|
|
1006
|
+
|
|
1007
|
+
case "studio-unignore-rule":
|
|
1008
|
+
await removeStudioIgnoreRule(element.dataset.pattern);
|
|
1009
|
+
return;
|
|
1010
|
+
|
|
1011
|
+
case "studio-close-file-tab":
|
|
1012
|
+
await closeStudioFileTab(element.dataset.path);
|
|
1013
|
+
return;
|
|
1014
|
+
|
|
601
1015
|
case "studio-toggle-folder":
|
|
602
|
-
toggleStudioFolder(element.dataset.folder);
|
|
1016
|
+
await toggleStudioFolder(element.dataset.folder);
|
|
603
1017
|
return;
|
|
604
1018
|
|
|
605
1019
|
case "studio-toggle-terminal":
|
|
606
1020
|
toggleStudioTerminal();
|
|
607
1021
|
return;
|
|
608
1022
|
|
|
1023
|
+
case "studio-toggle-files":
|
|
1024
|
+
toggleStudioPanel("files");
|
|
1025
|
+
return;
|
|
1026
|
+
|
|
1027
|
+
case "studio-toggle-sidebar":
|
|
1028
|
+
toggleStudioPanel("sidebar");
|
|
1029
|
+
return;
|
|
1030
|
+
|
|
1031
|
+
case "studio-open-sidebar-tab":
|
|
1032
|
+
openStudioSidebarTab(element.dataset.tab);
|
|
1033
|
+
return;
|
|
1034
|
+
|
|
609
1035
|
case "studio-save":
|
|
610
1036
|
await saveStudioFile();
|
|
611
1037
|
return;
|
|
@@ -614,6 +1040,14 @@ async function runAction(name, element) {
|
|
|
614
1040
|
await runStudioCheck();
|
|
615
1041
|
return;
|
|
616
1042
|
|
|
1043
|
+
case "studio-package-size":
|
|
1044
|
+
await refreshStudioPackageSize({ force: true, notify: true });
|
|
1045
|
+
return;
|
|
1046
|
+
|
|
1047
|
+
case "studio-toggle-theme":
|
|
1048
|
+
toggleStudioTheme();
|
|
1049
|
+
return;
|
|
1050
|
+
|
|
617
1051
|
case "studio-check-note":
|
|
618
1052
|
revealStudioCheckNote(Number(element.dataset.line || 1), Number(element.dataset.column || 1));
|
|
619
1053
|
return;
|
|
@@ -727,7 +1161,14 @@ async function runAction(name, element) {
|
|
|
727
1161
|
return;
|
|
728
1162
|
|
|
729
1163
|
case "studio-release-refresh":
|
|
730
|
-
await
|
|
1164
|
+
await Promise.all([
|
|
1165
|
+
loadStudioReleaseTags({ force: true }),
|
|
1166
|
+
refreshStudioIgnoreState({ files: true })
|
|
1167
|
+
]);
|
|
1168
|
+
return;
|
|
1169
|
+
|
|
1170
|
+
case "studio-release-toggle-ignored":
|
|
1171
|
+
toggleStudioReleaseIgnored();
|
|
731
1172
|
return;
|
|
732
1173
|
|
|
733
1174
|
case "studio-release-switch":
|
|
@@ -1089,6 +1530,7 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1089
1530
|
id,
|
|
1090
1531
|
plugin: null,
|
|
1091
1532
|
files: [],
|
|
1533
|
+
directories: [],
|
|
1092
1534
|
selectedFile: null,
|
|
1093
1535
|
fileContent: "",
|
|
1094
1536
|
draftContent: "",
|
|
@@ -1106,9 +1548,15 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1106
1548
|
playgroundUrl: "",
|
|
1107
1549
|
playgroundUrls: null,
|
|
1108
1550
|
activeTab: "editor",
|
|
1551
|
+
openFiles: [],
|
|
1109
1552
|
terminalOpen: true,
|
|
1110
1553
|
collapsedFolders: new Set(),
|
|
1111
|
-
|
|
1554
|
+
expandedIgnoredFolders: new Set(),
|
|
1555
|
+
loadingFolders: new Set(),
|
|
1556
|
+
terminal: [
|
|
1557
|
+
`Pressship Studio opened for ${scope === "local" ? "local plugin" : "WordPress.org plugin"} ${id}.`,
|
|
1558
|
+
{ message: `$ ${studioOpenCliCommand()}`, tone: "command" }
|
|
1559
|
+
],
|
|
1112
1560
|
aiPrompt: "",
|
|
1113
1561
|
aiJobId: null,
|
|
1114
1562
|
aiRunning: false,
|
|
@@ -1121,13 +1569,21 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1121
1569
|
editorKind: null,
|
|
1122
1570
|
editorModels: [],
|
|
1123
1571
|
layout: state.studio.layout ?? loadStudioLayout(),
|
|
1572
|
+
panels: normalizeStudioPanelState(state.studio.panels ?? loadStudioPanelState()),
|
|
1573
|
+
theme: normalizeStudioTheme(state.studio.theme ?? loadStudioTheme()),
|
|
1124
1574
|
sidebarTab,
|
|
1125
1575
|
playgroundVersionModal: null,
|
|
1126
1576
|
release: createInitialStudioRelease(),
|
|
1577
|
+
ignoreState: createInitialStudioIgnoreState(),
|
|
1578
|
+
packageSize: createInitialStudioPackageSize(),
|
|
1579
|
+
ignoreLoading: false,
|
|
1580
|
+
ignoreError: "",
|
|
1581
|
+
ignoreBusyPattern: "",
|
|
1582
|
+
ignoreBusyPath: "",
|
|
1127
1583
|
pendingConfirms: new Map()
|
|
1128
1584
|
};
|
|
1129
1585
|
saveStudioSidebarTab(pluginKey, sidebarTab);
|
|
1130
|
-
showView("studio");
|
|
1586
|
+
await showView("studio", { updateRoute: false });
|
|
1131
1587
|
renderStudio();
|
|
1132
1588
|
if (scope === "local" && sidebarTab === "release") {
|
|
1133
1589
|
void loadStudioReleaseTags();
|
|
@@ -1139,33 +1595,56 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1139
1595
|
state.studio.loading = false;
|
|
1140
1596
|
|
|
1141
1597
|
if (scope === "local") {
|
|
1142
|
-
const [result, checkState] = await Promise.all([
|
|
1598
|
+
const [result, checkState, ignoreState] = await Promise.all([
|
|
1143
1599
|
api(`/api/plugins/local/${encodeURIComponent(id)}/files`),
|
|
1144
|
-
api(`/api/plugins/local/${encodeURIComponent(id)}/check-state`).catch(() => ({ state: null }))
|
|
1600
|
+
api(`/api/plugins/local/${encodeURIComponent(id)}/check-state`).catch(() => ({ state: null })),
|
|
1601
|
+
api(`/api/plugins/local/${encodeURIComponent(id)}/ignore-state`).catch(() => createInitialStudioIgnoreState())
|
|
1145
1602
|
]);
|
|
1146
1603
|
state.studio.files = result.files ?? [];
|
|
1604
|
+
state.studio.directories = result.directories ?? [];
|
|
1147
1605
|
applyStudioCheckState(checkState.state);
|
|
1606
|
+
applyStudioIgnoreState(ignoreState);
|
|
1148
1607
|
renderStudio();
|
|
1149
|
-
const
|
|
1608
|
+
const requestedFile = options.filePath
|
|
1609
|
+
? state.studio.files.find((file) => file.path === options.filePath)
|
|
1610
|
+
: null;
|
|
1611
|
+
if (options.filePath && !requestedFile) {
|
|
1612
|
+
appendStudioTerminal(`Route file not found: ${options.filePath}`, "warning");
|
|
1613
|
+
}
|
|
1614
|
+
const initialFile = requestedFile ?? chooseInitialStudioFile(state.studio.files, state.studio.plugin?.slug);
|
|
1150
1615
|
if (initialFile) {
|
|
1151
|
-
await selectStudioFile(initialFile.path
|
|
1616
|
+
await selectStudioFile(initialFile.path, {
|
|
1617
|
+
updateRoute: options.updateRoute,
|
|
1618
|
+
replaceRoute: options.replaceRoute
|
|
1619
|
+
});
|
|
1152
1620
|
} else {
|
|
1153
1621
|
state.studio.draftContent = "";
|
|
1154
1622
|
remountStudioEditorIfNeeded();
|
|
1623
|
+
if (options.updateRoute !== false) {
|
|
1624
|
+
updateRouteFromState({ replace: options.replaceRoute });
|
|
1625
|
+
}
|
|
1155
1626
|
}
|
|
1627
|
+
void refreshStudioPackageSize({ render: true });
|
|
1156
1628
|
} else {
|
|
1157
1629
|
state.studio.files = [{ path: "readme.txt", name: "readme.txt", directory: "", size: detail.readme?.length ?? 0 }];
|
|
1158
1630
|
state.studio.selectedFile = state.studio.files[0];
|
|
1631
|
+
state.studio.openFiles = ["readme.txt"];
|
|
1159
1632
|
state.studio.fileContent = detail.readme ?? "No hosted readme.txt could be loaded.";
|
|
1160
1633
|
state.studio.draftContent = state.studio.fileContent;
|
|
1161
1634
|
state.studio.readOnly = true;
|
|
1162
1635
|
renderStudio();
|
|
1163
1636
|
remountStudioEditorIfNeeded();
|
|
1637
|
+
if (options.updateRoute !== false) {
|
|
1638
|
+
updateRouteFromState({ replace: options.replaceRoute });
|
|
1639
|
+
}
|
|
1164
1640
|
}
|
|
1165
1641
|
} catch (error) {
|
|
1166
1642
|
state.studio.loading = false;
|
|
1167
1643
|
appendStudioTerminal(error.message, "error");
|
|
1168
1644
|
renderStudio();
|
|
1645
|
+
if (options.updateRoute !== false) {
|
|
1646
|
+
updateRouteFromState({ replace: options.replaceRoute });
|
|
1647
|
+
}
|
|
1169
1648
|
}
|
|
1170
1649
|
}
|
|
1171
1650
|
|
|
@@ -1188,6 +1667,7 @@ async function selectStudioFile(relativePath, options = {}) {
|
|
|
1188
1667
|
return;
|
|
1189
1668
|
}
|
|
1190
1669
|
|
|
1670
|
+
ensureStudioFileTab(relativePath);
|
|
1191
1671
|
state.studio.selectedFile = state.studio.files.find((file) => file.path === relativePath) ?? {
|
|
1192
1672
|
path: relativePath,
|
|
1193
1673
|
name: relativePath.split("/").pop() ?? relativePath,
|
|
@@ -1200,6 +1680,9 @@ async function selectStudioFile(relativePath, options = {}) {
|
|
|
1200
1680
|
state.studio.activeTab = "editor";
|
|
1201
1681
|
renderStudio();
|
|
1202
1682
|
remountStudioEditorIfNeeded();
|
|
1683
|
+
if (options.updateRoute !== false) {
|
|
1684
|
+
updateRouteFromState({ replace: options.replaceRoute });
|
|
1685
|
+
}
|
|
1203
1686
|
|
|
1204
1687
|
try {
|
|
1205
1688
|
const result = await api(
|
|
@@ -1213,8 +1696,100 @@ async function selectStudioFile(relativePath, options = {}) {
|
|
|
1213
1696
|
remountStudioEditorIfNeeded();
|
|
1214
1697
|
} catch (error) {
|
|
1215
1698
|
appendStudioTerminal(error.message, "error");
|
|
1699
|
+
state.studio.fileContent = `Cannot open ${relativePath}.\n\n${error.message}`;
|
|
1700
|
+
state.studio.draftContent = state.studio.fileContent;
|
|
1701
|
+
state.studio.dirty = false;
|
|
1702
|
+
renderStudio();
|
|
1703
|
+
remountStudioEditorIfNeeded();
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
function syncSelectedStudioFileOnResume() {
|
|
1708
|
+
if (Date.now() - studioLastExternalSyncAt < 1200) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
studioLastExternalSyncAt = Date.now();
|
|
1712
|
+
void syncSelectedStudioFileFromDisk({ reason: "external" });
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
async function syncSelectedStudioFileFromDisk(options = {}) {
|
|
1716
|
+
if (
|
|
1717
|
+
studioFileSyncInFlight ||
|
|
1718
|
+
state.studio.scope !== "local" ||
|
|
1719
|
+
!state.studio.id ||
|
|
1720
|
+
!state.studio.selectedFile?.path ||
|
|
1721
|
+
state.studio.activeTab !== "editor" ||
|
|
1722
|
+
state.studio.loading ||
|
|
1723
|
+
state.studio.readOnly ||
|
|
1724
|
+
studioAiChangedFile(state.studio.selectedFile.path)
|
|
1725
|
+
) {
|
|
1726
|
+
return false;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
const selectedPath = state.studio.selectedFile.path;
|
|
1730
|
+
studioFileSyncInFlight = true;
|
|
1731
|
+
captureStudioEditorValue();
|
|
1732
|
+
|
|
1733
|
+
try {
|
|
1734
|
+
const result = await api(
|
|
1735
|
+
`/api/plugins/local/${encodeURIComponent(state.studio.id)}/files/content?path=${encodeURIComponent(selectedPath)}`
|
|
1736
|
+
);
|
|
1737
|
+
if (state.studio.selectedFile?.path !== selectedPath) {
|
|
1738
|
+
return false;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
const diskContent = result.content ?? "";
|
|
1742
|
+
if (diskContent === state.studio.fileContent) {
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
if (state.studio.dirty && state.studio.draftContent !== diskContent) {
|
|
1747
|
+
const conflictKey = `${selectedPath}:${diskContent.length}:${diskContent.slice(0, 80)}`;
|
|
1748
|
+
if (studioLastDiskConflictKey !== conflictKey) {
|
|
1749
|
+
studioLastDiskConflictKey = conflictKey;
|
|
1750
|
+
appendStudioTerminal(
|
|
1751
|
+
`${selectedPath} changed on disk. Your unsaved editor changes were kept.`,
|
|
1752
|
+
"warning"
|
|
1753
|
+
);
|
|
1754
|
+
notice(`${selectedPath} changed on disk. Unsaved editor changes were kept.`, "warning");
|
|
1755
|
+
}
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
studioLastDiskConflictKey = "";
|
|
1760
|
+
state.studio.selectedFile =
|
|
1761
|
+
state.studio.files.find((file) => file.path === result.path) ?? state.studio.selectedFile;
|
|
1762
|
+
state.studio.fileContent = diskContent;
|
|
1763
|
+
state.studio.draftContent = diskContent;
|
|
1764
|
+
state.studio.dirty = false;
|
|
1216
1765
|
renderStudio();
|
|
1766
|
+
remountStudioEditorIfNeeded();
|
|
1767
|
+
updateStudioControls();
|
|
1768
|
+
if (options.reason === "ignore-rule") {
|
|
1769
|
+
appendStudioTerminal(`Reloaded ${selectedPath} after ignore rules changed.`, "success");
|
|
1770
|
+
} else if (options.reason === "external") {
|
|
1771
|
+
appendStudioTerminal(`Reloaded ${selectedPath} from disk.`, "status");
|
|
1772
|
+
}
|
|
1773
|
+
return true;
|
|
1774
|
+
} catch (error) {
|
|
1775
|
+
if (options.reportErrors) {
|
|
1776
|
+
appendStudioTerminal(`Could not refresh ${selectedPath}: ${error.message}`, "error");
|
|
1777
|
+
}
|
|
1778
|
+
return false;
|
|
1779
|
+
} finally {
|
|
1780
|
+
studioFileSyncInFlight = false;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
function ensureStudioFileTab(relativePath) {
|
|
1785
|
+
if (!relativePath) {
|
|
1786
|
+
return;
|
|
1217
1787
|
}
|
|
1788
|
+
const openFiles = Array.isArray(state.studio.openFiles) ? state.studio.openFiles : [];
|
|
1789
|
+
if (!openFiles.includes(relativePath)) {
|
|
1790
|
+
openFiles.push(relativePath);
|
|
1791
|
+
}
|
|
1792
|
+
state.studio.openFiles = openFiles;
|
|
1218
1793
|
}
|
|
1219
1794
|
|
|
1220
1795
|
async function saveStudioFile() {
|
|
@@ -1238,6 +1813,10 @@ async function saveStudioFile() {
|
|
|
1238
1813
|
state.studio.draftContent = content;
|
|
1239
1814
|
state.studio.dirty = false;
|
|
1240
1815
|
appendStudioTerminal(`Saved ${state.studio.selectedFile.path}.`, "success");
|
|
1816
|
+
markStudioPackageSizeStale();
|
|
1817
|
+
if (state.studio.selectedFile.path === ".pressshipignore") {
|
|
1818
|
+
await refreshStudioIgnoreState({ files: true, render: false });
|
|
1819
|
+
}
|
|
1241
1820
|
renderStudio();
|
|
1242
1821
|
remountStudioEditorIfNeeded();
|
|
1243
1822
|
updateStudioControls();
|
|
@@ -1472,6 +2051,7 @@ async function selectStudioAiChange(filePath) {
|
|
|
1472
2051
|
state.studio.activeTab = "editor";
|
|
1473
2052
|
renderStudio();
|
|
1474
2053
|
remountStudioEditorIfNeeded();
|
|
2054
|
+
updateRouteFromState();
|
|
1475
2055
|
updateStudioAiSidebar();
|
|
1476
2056
|
}
|
|
1477
2057
|
|
|
@@ -1501,6 +2081,10 @@ async function acceptStudioAiChange(filePath) {
|
|
|
1501
2081
|
applyStudioCheckState(result.checkState);
|
|
1502
2082
|
}
|
|
1503
2083
|
state.studio.files = result.files ?? state.studio.files;
|
|
2084
|
+
state.studio.directories = result.directories ?? state.studio.directories;
|
|
2085
|
+
if (change.path === ".pressshipignore") {
|
|
2086
|
+
await refreshStudioIgnoreState({ files: true, render: false });
|
|
2087
|
+
}
|
|
1504
2088
|
removeStudioAiChangedFile(change.path);
|
|
1505
2089
|
if (state.studio.selectedFile?.path === change.path) {
|
|
1506
2090
|
if (change.status === "deleted") {
|
|
@@ -1571,6 +2155,35 @@ function renderStudio() {
|
|
|
1571
2155
|
? "WordPress.org plugin"
|
|
1572
2156
|
: "Choose a plugin from WordPress.org or Local Library.";
|
|
1573
2157
|
|
|
2158
|
+
if (state.studio.loading && !state.studio.scope && state.studio.id) {
|
|
2159
|
+
const fileLabel = state.studio.selectedFile?.path
|
|
2160
|
+
? `Restoring ${state.studio.selectedFile.path}.`
|
|
2161
|
+
: "Restoring the workspace.";
|
|
2162
|
+
els.studio.innerHTML = `
|
|
2163
|
+
<div class="studio-root studio-empty-root">
|
|
2164
|
+
<div class="studio-empty-state" aria-label="Opening Studio workspace">
|
|
2165
|
+
<section class="studio-empty-copy">
|
|
2166
|
+
<span class="studio-empty-kicker">
|
|
2167
|
+
<span class="dashicons dashicons-editor-code" aria-hidden="true"></span>
|
|
2168
|
+
Pressship Studio
|
|
2169
|
+
</span>
|
|
2170
|
+
<h1>Opening ${escapeHtml(title)}</h1>
|
|
2171
|
+
<p class="studio-empty-subtitle">${escapeHtml(fileLabel)}</p>
|
|
2172
|
+
</section>
|
|
2173
|
+
<section class="studio-empty-panel" aria-label="Loading workspace">
|
|
2174
|
+
${loadingShell("Loading Studio workspace...")}
|
|
2175
|
+
</section>
|
|
2176
|
+
<div class="studio-empty-footer">
|
|
2177
|
+
<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
2178
|
+
Matching the URL to a local or WordPress.org plugin.
|
|
2179
|
+
</div>
|
|
2180
|
+
</div>
|
|
2181
|
+
</div>
|
|
2182
|
+
`;
|
|
2183
|
+
updateStudioControls();
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
1574
2187
|
if (!state.studio.id) {
|
|
1575
2188
|
const pickerOptions = studioPickerOptions();
|
|
1576
2189
|
const pickerDisabled = pickerOptions.length ? "" : "disabled";
|
|
@@ -1632,63 +2245,76 @@ function renderStudio() {
|
|
|
1632
2245
|
return;
|
|
1633
2246
|
}
|
|
1634
2247
|
|
|
1635
|
-
const
|
|
1636
|
-
|
|
2248
|
+
const hasExplorerEntries = state.studio.files.length || state.studio.directories.length;
|
|
2249
|
+
const fileList = hasExplorerEntries
|
|
2250
|
+
? renderStudioFileTree(buildStudioFileTree(state.studio.files, state.studio.directories))
|
|
1637
2251
|
: `<p class="studio-muted">No editable text files found.</p>`;
|
|
1638
|
-
const editorTabLabel = state.studio.selectedFile?.path ?? "Editor";
|
|
1639
2252
|
const playgroundPort = state.studio.playgroundUrl ? new URL(state.studio.playgroundUrl).port : "";
|
|
2253
|
+
const panels = normalizeStudioPanelState(state.studio.panels);
|
|
1640
2254
|
|
|
1641
2255
|
els.studio.innerHTML = `
|
|
1642
|
-
<div class="studio-root${state.studio.terminalOpen ? " has-terminal" : ""}">
|
|
2256
|
+
<div class="studio-root${state.studio.terminalOpen ? " has-terminal" : ""}${panels.files ? " has-files" : " is-files-collapsed"}${panels.sidebar ? " has-secondary-sidebar" : " is-secondary-sidebar-collapsed"}" data-theme="${escapeAttr(studioTheme())}">
|
|
1643
2257
|
<header class="studio-titlebar">
|
|
1644
2258
|
<div class="studio-title">
|
|
1645
2259
|
<strong>${escapeHtml(title)}</strong>
|
|
1646
2260
|
<span>${escapeHtml(source)}</span>
|
|
1647
2261
|
</div>
|
|
1648
2262
|
<div class="studio-title-actions">
|
|
1649
|
-
<
|
|
1650
|
-
<
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
</
|
|
1661
|
-
|
|
1662
|
-
</header>
|
|
1663
|
-
<div class="studio-main">
|
|
1664
|
-
<aside class="studio-files" aria-label="Plugin files">
|
|
1665
|
-
<div class="studio-file-list">${fileList}</div>
|
|
1666
|
-
</aside>
|
|
1667
|
-
${renderStudioResizer("files", "h", { label: "files panel" })}
|
|
1668
|
-
<section class="studio-workbench" aria-label="Studio editor">
|
|
1669
|
-
<div class="studio-tabs" aria-label="Studio tabs and Playground controls">
|
|
1670
|
-
<div class="studio-tablist" role="tablist" aria-label="Studio tabs">
|
|
1671
|
-
<button type="button" role="tab" aria-selected="${state.studio.activeTab === "editor" ? "true" : "false"}" class="studio-tab-button studio-editor-tab${state.studio.activeTab === "editor" ? " is-active" : ""}" data-action="studio-tab" data-tab="editor">
|
|
1672
|
-
<span class="dashicons ${studioFileIcon(editorTabLabel)}" aria-hidden="true"></span>
|
|
1673
|
-
<span>${escapeHtml(editorTabLabel)}</span>
|
|
1674
|
-
${state.studio.dirty ? `<em aria-label="Unsaved changes"></em>` : ""}
|
|
1675
|
-
</button>
|
|
1676
|
-
<button type="button" role="tab" aria-selected="${state.studio.activeTab === "home" ? "true" : "false"}" class="studio-tab-button studio-preview-tab${state.studio.activeTab === "home" ? " is-active" : ""}" data-action="studio-tab" data-tab="home">
|
|
1677
|
-
<span class="dashicons dashicons-admin-home" aria-hidden="true"></span>
|
|
1678
|
-
<span>Home</span>
|
|
1679
|
-
${playgroundPort ? `<small>${escapeHtml(`:${playgroundPort}`)}</small>` : ""}
|
|
1680
|
-
</button>
|
|
1681
|
-
<button type="button" role="tab" aria-selected="${state.studio.activeTab === "admin" ? "true" : "false"}" class="studio-tab-button studio-preview-tab${state.studio.activeTab === "admin" ? " is-active" : ""}" data-action="studio-tab" data-tab="admin">
|
|
1682
|
-
<span class="dashicons dashicons-admin-site-alt3" aria-hidden="true"></span>
|
|
1683
|
-
<span>WP Admin</span>
|
|
1684
|
-
${state.studio.playgroundUrl ? `<small>admin/password</small>` : ""}
|
|
1685
|
-
</button>
|
|
1686
|
-
</div>
|
|
2263
|
+
<div class="studio-toolbar-group studio-toolbar-layout" aria-label="Workbench layout">
|
|
2264
|
+
<button class="studio-layout-button${panels.files ? " is-active" : ""}" type="button" data-action="studio-toggle-files" aria-pressed="${panels.files ? "true" : "false"}" aria-label="${panels.files ? "Hide Explorer" : "Show Explorer"}" title="${panels.files ? "Hide Explorer" : "Show Explorer"}">
|
|
2265
|
+
<span class="dashicons dashicons-align-left" aria-hidden="true"></span>
|
|
2266
|
+
</button>
|
|
2267
|
+
<button class="studio-icon-button studio-compact-button" type="button" data-action="studio-toggle-terminal" aria-pressed="${state.studio.terminalOpen ? "true" : "false"}" aria-label="${state.studio.terminalOpen ? "Hide Terminal" : "Show Terminal"}" title="${state.studio.terminalOpen ? "Hide Terminal" : "Show Terminal"}">
|
|
2268
|
+
<span class="dashicons dashicons-editor-kitchensink" aria-hidden="true"></span>
|
|
2269
|
+
<span>Terminal</span>
|
|
2270
|
+
</button>
|
|
2271
|
+
<button class="studio-layout-button${panels.sidebar ? " is-active" : ""}" type="button" data-action="studio-toggle-sidebar" aria-pressed="${panels.sidebar ? "true" : "false"}" aria-label="${panels.sidebar ? "Hide Secondary Side Bar" : "Show Secondary Side Bar"}" title="${panels.sidebar ? "Hide Secondary Side Bar" : "Show Secondary Side Bar"}">
|
|
2272
|
+
<span class="dashicons dashicons-align-right" aria-hidden="true"></span>
|
|
2273
|
+
</button>
|
|
2274
|
+
</div>
|
|
2275
|
+
<div class="studio-toolbar-group studio-toolbar-run" aria-label="Playground">
|
|
1687
2276
|
${renderStudioPlayButton()}
|
|
1688
|
-
<span class="studio-preview-state${state.studio.running ? " is-loading" : state.studio.playgroundUrl ? " is-ready" : ""}">
|
|
2277
|
+
<span class="studio-preview-state${state.studio.running ? " is-loading" : state.studio.playgroundUrl ? " is-ready" : ""}" title="${escapeAttr(studioPreviewStateTitle())}">
|
|
1689
2278
|
<span aria-hidden="true"></span>
|
|
1690
|
-
|
|
2279
|
+
<em>${escapeHtml(studioPreviewStateLabel())}</em>
|
|
1691
2280
|
</span>
|
|
2281
|
+
</div>
|
|
2282
|
+
<div class="studio-toolbar-group studio-toolbar-actions" aria-label="Editor actions">
|
|
2283
|
+
<button class="studio-action-button studio-compact-button" type="button" data-action="studio-save" id="studio-save-button" aria-label="${escapeAttr(`Save ${studioSaveShortcutLabel()}`)}" aria-keyshortcuts="${escapeAttr(studioSaveAriaShortcut())}" title="${escapeAttr(`Save ${studioSaveShortcutLabel()}`)}" disabled>
|
|
2284
|
+
<span class="dashicons dashicons-saved" aria-hidden="true"></span>
|
|
2285
|
+
<span>Save</span>
|
|
2286
|
+
</button>
|
|
2287
|
+
<button class="studio-action-button studio-compact-button" type="button" data-action="studio-check" id="studio-check-button" aria-label="Run Plugin Check" title="Run Plugin Check" disabled>
|
|
2288
|
+
<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>
|
|
2289
|
+
<span>Check</span>
|
|
2290
|
+
</button>
|
|
2291
|
+
${renderStudioPackageSizeButton()}
|
|
2292
|
+
${renderStudioThemeToggle()}
|
|
2293
|
+
</div>
|
|
2294
|
+
</div>
|
|
2295
|
+
</header>
|
|
2296
|
+
<div class="studio-main">
|
|
2297
|
+
${renderStudioActivityBar(panels)}
|
|
2298
|
+
${
|
|
2299
|
+
panels.files
|
|
2300
|
+
? `<aside class="studio-files" aria-label="Explorer">
|
|
2301
|
+
<header class="studio-pane-header">
|
|
2302
|
+
<strong>Explorer</strong>
|
|
2303
|
+
<button class="studio-pane-action" type="button" data-action="studio-toggle-files" aria-label="Hide Explorer" title="Hide Explorer">
|
|
2304
|
+
<span class="dashicons dashicons-no-alt" aria-hidden="true"></span>
|
|
2305
|
+
</button>
|
|
2306
|
+
</header>
|
|
2307
|
+
<div class="studio-file-list">${fileList}</div>
|
|
2308
|
+
</aside>
|
|
2309
|
+
${renderStudioResizer("files", "h", { label: "Explorer" })}`
|
|
2310
|
+
: ""
|
|
2311
|
+
}
|
|
2312
|
+
<section class="studio-workbench" aria-label="Studio editor">
|
|
2313
|
+
<div class="studio-tabs" aria-label="Studio tabs and Playground controls">
|
|
2314
|
+
<div class="studio-tablist" role="tablist" aria-label="Studio tabs">
|
|
2315
|
+
${renderStudioPinnedPreviewTabs(playgroundPort)}
|
|
2316
|
+
${renderStudioFileTabs()}
|
|
2317
|
+
</div>
|
|
1692
2318
|
<span class="studio-tab-spacer"></span>
|
|
1693
2319
|
<span id="studio-editor-status">${escapeHtml(studioEditorStatusLabel())}</span>
|
|
1694
2320
|
</div>
|
|
@@ -1706,23 +2332,202 @@ function renderStudio() {
|
|
|
1706
2332
|
<div id="studio-terminal-output" class="studio-terminal-output">
|
|
1707
2333
|
${renderStudioTerminal()}
|
|
1708
2334
|
</div>
|
|
1709
|
-
|
|
2335
|
+
</section>`
|
|
1710
2336
|
: ""
|
|
1711
2337
|
}
|
|
1712
2338
|
</section>
|
|
1713
|
-
${
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2339
|
+
${
|
|
2340
|
+
panels.sidebar
|
|
2341
|
+
? `${renderStudioResizer("ai", "h", { invert: true, label: "Secondary Side Bar" })}
|
|
2342
|
+
<aside class="studio-ai" id="studio-ai" aria-label="Secondary Side Bar">
|
|
2343
|
+
${renderStudioAiSidebar()}
|
|
2344
|
+
</aside>`
|
|
2345
|
+
: ""
|
|
2346
|
+
}
|
|
1717
2347
|
</div>
|
|
2348
|
+
${renderStudioStatusBar()}
|
|
1718
2349
|
${renderStudioPlaygroundVersionModal()}
|
|
1719
2350
|
</div>
|
|
1720
2351
|
`;
|
|
1721
2352
|
applyStudioLayout(els.studio.querySelector(".studio-root"));
|
|
1722
2353
|
bindStudioResizers();
|
|
2354
|
+
scrollActiveStudioFileTabIntoView();
|
|
1723
2355
|
updateStudioControls();
|
|
1724
2356
|
}
|
|
1725
2357
|
|
|
2358
|
+
function renderStudioActivityBar(panels) {
|
|
2359
|
+
const tab = state.studio.sidebarTab === "release" ? "release" : "ai";
|
|
2360
|
+
const activityButton = ({ action, icon, label, active, tab: targetTab }) => `
|
|
2361
|
+
<button class="studio-activity-button${active ? " is-active" : ""}" type="button" data-action="${escapeAttr(action)}"${targetTab ? ` data-tab="${escapeAttr(targetTab)}"` : ""} aria-pressed="${active ? "true" : "false"}" aria-label="${escapeAttr(label)}" title="${escapeAttr(label)}">
|
|
2362
|
+
<span class="dashicons ${escapeAttr(icon)}" aria-hidden="true"></span>
|
|
2363
|
+
</button>
|
|
2364
|
+
`;
|
|
2365
|
+
|
|
2366
|
+
return `
|
|
2367
|
+
<nav class="studio-activitybar" aria-label="Studio workbench views">
|
|
2368
|
+
<div class="studio-activitybar-primary">
|
|
2369
|
+
${activityButton({
|
|
2370
|
+
action: "studio-toggle-files",
|
|
2371
|
+
icon: "dashicons-open-folder",
|
|
2372
|
+
label: panels.files ? "Hide Explorer" : "Show Explorer",
|
|
2373
|
+
active: panels.files
|
|
2374
|
+
})}
|
|
2375
|
+
${activityButton({
|
|
2376
|
+
action: "studio-tab",
|
|
2377
|
+
icon: "dashicons-editor-code",
|
|
2378
|
+
label: "Editor",
|
|
2379
|
+
active: state.studio.activeTab === "editor",
|
|
2380
|
+
tab: "editor"
|
|
2381
|
+
})}
|
|
2382
|
+
${activityButton({
|
|
2383
|
+
action: "studio-tab",
|
|
2384
|
+
icon: "dashicons-admin-home",
|
|
2385
|
+
label: "Playground Home",
|
|
2386
|
+
active: state.studio.activeTab === "home",
|
|
2387
|
+
tab: "home"
|
|
2388
|
+
})}
|
|
2389
|
+
</div>
|
|
2390
|
+
<div class="studio-activitybar-secondary">
|
|
2391
|
+
${activityButton({
|
|
2392
|
+
action: "studio-open-sidebar-tab",
|
|
2393
|
+
icon: "dashicons-format-chat",
|
|
2394
|
+
label: "AI Helper",
|
|
2395
|
+
active: panels.sidebar && tab === "ai",
|
|
2396
|
+
tab: "ai"
|
|
2397
|
+
})}
|
|
2398
|
+
${activityButton({
|
|
2399
|
+
action: "studio-open-sidebar-tab",
|
|
2400
|
+
icon: "ps-icon-rocket",
|
|
2401
|
+
label: "Release",
|
|
2402
|
+
active: panels.sidebar && tab === "release",
|
|
2403
|
+
tab: "release"
|
|
2404
|
+
})}
|
|
2405
|
+
${activityButton({
|
|
2406
|
+
action: "studio-toggle-terminal",
|
|
2407
|
+
icon: "dashicons-editor-kitchensink",
|
|
2408
|
+
label: state.studio.terminalOpen ? "Hide Terminal" : "Show Terminal",
|
|
2409
|
+
active: state.studio.terminalOpen
|
|
2410
|
+
})}
|
|
2411
|
+
</div>
|
|
2412
|
+
</nav>
|
|
2413
|
+
`;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
function renderStudioStatusBar() {
|
|
2417
|
+
const plugin = state.studio.plugin;
|
|
2418
|
+
const file = state.studio.selectedFile?.path ?? "No file selected";
|
|
2419
|
+
const check = state.studio.checkSummary
|
|
2420
|
+
? `${state.studio.checkSummary.error || 0} errors, ${state.studio.checkSummary.warning || 0} warnings`
|
|
2421
|
+
: "Plugin Check idle";
|
|
2422
|
+
const assistant = selectedStudioAiAssistant();
|
|
2423
|
+
return `
|
|
2424
|
+
<footer class="studio-statusbar" aria-label="Studio status">
|
|
2425
|
+
<span><span class="dashicons dashicons-admin-plugins" aria-hidden="true"></span>${escapeHtml(plugin?.slug ?? state.studio.id ?? "Studio")}</span>
|
|
2426
|
+
<span>${escapeHtml(file)}</span>
|
|
2427
|
+
<span>${escapeHtml(check)}</span>
|
|
2428
|
+
<span class="studio-statusbar-spacer"></span>
|
|
2429
|
+
<span>${escapeHtml(assistant === "none" ? "AI disabled" : assistantLabel(assistant))}</span>
|
|
2430
|
+
<span>${escapeHtml(state.studio.readOnly ? "Read-only" : "Writable")}</span>
|
|
2431
|
+
</footer>
|
|
2432
|
+
`;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function renderStudioPinnedPreviewTabs(playgroundPort) {
|
|
2436
|
+
return `
|
|
2437
|
+
<span class="studio-pinned-tabs" aria-label="Pinned preview tabs">
|
|
2438
|
+
<button type="button" role="tab" aria-selected="${state.studio.activeTab === "home" ? "true" : "false"}" class="studio-tab-button studio-tab-pinned studio-preview-tab${state.studio.activeTab === "home" ? " is-active" : ""}" data-action="studio-tab" data-tab="home" title="Home">
|
|
2439
|
+
<span class="dashicons dashicons-admin-home" aria-hidden="true"></span>
|
|
2440
|
+
<span>Home</span>
|
|
2441
|
+
${playgroundPort ? `<small>${escapeHtml(`:${playgroundPort}`)}</small>` : ""}
|
|
2442
|
+
</button>
|
|
2443
|
+
<button type="button" role="tab" aria-selected="${state.studio.activeTab === "admin" ? "true" : "false"}" class="studio-tab-button studio-tab-pinned studio-preview-tab${state.studio.activeTab === "admin" ? " is-active" : ""}" data-action="studio-tab" data-tab="admin" title="WP Admin">
|
|
2444
|
+
<span class="dashicons dashicons-admin-site-alt3" aria-hidden="true"></span>
|
|
2445
|
+
<span>WP Admin</span>
|
|
2446
|
+
${state.studio.playgroundUrl ? `<small>admin/password</small>` : ""}
|
|
2447
|
+
</button>
|
|
2448
|
+
</span>
|
|
2449
|
+
`;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
function renderStudioFileTabs() {
|
|
2453
|
+
const openFiles = studioOpenFileTabs();
|
|
2454
|
+
if (!openFiles.length) {
|
|
2455
|
+
return `<span class="studio-file-tabs-empty">Open a file from Explorer</span>`;
|
|
2456
|
+
}
|
|
2457
|
+
return `
|
|
2458
|
+
<span class="studio-file-tabs" aria-label="Open files">
|
|
2459
|
+
${openFiles.map((file) => renderStudioFileTab(file)).join("")}
|
|
2460
|
+
</span>
|
|
2461
|
+
`;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
function studioOpenFileTabs() {
|
|
2465
|
+
const knownFiles = new Map(state.studio.files.map((file) => [file.path, file]));
|
|
2466
|
+
const selectedPath = state.studio.selectedFile?.path;
|
|
2467
|
+
const paths = Array.isArray(state.studio.openFiles) ? [...state.studio.openFiles] : [];
|
|
2468
|
+
if (selectedPath && !paths.includes(selectedPath)) {
|
|
2469
|
+
paths.push(selectedPath);
|
|
2470
|
+
}
|
|
2471
|
+
state.studio.openFiles = paths;
|
|
2472
|
+
return paths.map((path) => knownFiles.get(path) ?? {
|
|
2473
|
+
path,
|
|
2474
|
+
name: path.split("/").pop() ?? path,
|
|
2475
|
+
directory: path.includes("/") ? path.split("/").slice(0, -1).join("/") : "",
|
|
2476
|
+
size: 0
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
function renderStudioFileTab(file) {
|
|
2481
|
+
const current = state.studio.activeTab === "editor" && file.path === state.studio.selectedFile?.path;
|
|
2482
|
+
const dirty = current && state.studio.dirty;
|
|
2483
|
+
return `
|
|
2484
|
+
<span class="studio-file-tab-wrap">
|
|
2485
|
+
<button type="button" role="tab" aria-selected="${current ? "true" : "false"}" class="studio-tab-button studio-editor-tab${current ? " is-active" : ""}" data-action="studio-file" data-path="${escapeAttr(file.path)}" title="${escapeAttr(file.path)}">
|
|
2486
|
+
<span class="dashicons ${studioFileIcon(file.path)}" aria-hidden="true"></span>
|
|
2487
|
+
<span>${escapeHtml(file.name ?? file.path)}</span>
|
|
2488
|
+
${dirty ? `<em aria-label="Unsaved changes"></em>` : ""}
|
|
2489
|
+
</button>
|
|
2490
|
+
<button type="button" class="studio-tab-close" data-action="studio-close-file-tab" data-path="${escapeAttr(file.path)}" aria-label="Close ${escapeAttr(file.name ?? file.path)}" title="Close">
|
|
2491
|
+
<span class="dashicons dashicons-no-alt" aria-hidden="true"></span>
|
|
2492
|
+
</button>
|
|
2493
|
+
</span>
|
|
2494
|
+
`;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
function scrollActiveStudioFileTabIntoView() {
|
|
2498
|
+
if (state.studio.activeTab !== "editor" || !state.studio.selectedFile?.path) {
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
requestAnimationFrame(() => {
|
|
2502
|
+
const container = document.querySelector(".studio-file-tabs");
|
|
2503
|
+
if (!container) {
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
const selectedPath = state.studio.selectedFile?.path;
|
|
2507
|
+
const activeTab = Array.from(container.querySelectorAll(".studio-tab-button[data-path]")).find(
|
|
2508
|
+
(button) => button.dataset.path === selectedPath
|
|
2509
|
+
);
|
|
2510
|
+
const target = activeTab?.closest(".studio-file-tab-wrap") ?? activeTab;
|
|
2511
|
+
if (!target) {
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
const padding = 12;
|
|
2516
|
+
const containerRect = container.getBoundingClientRect();
|
|
2517
|
+
const targetRect = target.getBoundingClientRect();
|
|
2518
|
+
const targetLeft = targetRect.left - containerRect.left + container.scrollLeft;
|
|
2519
|
+
const targetRight = targetLeft + targetRect.width;
|
|
2520
|
+
const visibleLeft = container.scrollLeft;
|
|
2521
|
+
const visibleRight = visibleLeft + container.clientWidth;
|
|
2522
|
+
|
|
2523
|
+
if (targetLeft < visibleLeft + padding) {
|
|
2524
|
+
container.scrollLeft = Math.max(0, targetLeft - padding);
|
|
2525
|
+
} else if (targetRight > visibleRight - padding) {
|
|
2526
|
+
container.scrollLeft = targetRight - container.clientWidth + padding;
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
|
|
1726
2531
|
function renderStudioPlaygroundVersionModal() {
|
|
1727
2532
|
const modal = state.studio.playgroundVersionModal;
|
|
1728
2533
|
if (!modal) {
|
|
@@ -1879,14 +2684,101 @@ function renderStudioPlayButton() {
|
|
|
1879
2684
|
`;
|
|
1880
2685
|
}
|
|
1881
2686
|
|
|
2687
|
+
function studioSaveShortcutLabel() {
|
|
2688
|
+
return isMac ? "(⌘S)" : "(Ctrl+S)";
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
function studioSaveAriaShortcut() {
|
|
2692
|
+
return isMac ? "Meta+S" : "Control+S";
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
function renderStudioPackageSizeButton() {
|
|
2696
|
+
if (state.studio.scope !== "local") {
|
|
2697
|
+
return "";
|
|
2698
|
+
}
|
|
2699
|
+
const packageSize = state.studio.packageSize ?? createInitialStudioPackageSize();
|
|
2700
|
+
const hasSize = Number.isFinite(packageSize.sizeBytes);
|
|
2701
|
+
const label = packageSize.loading
|
|
2702
|
+
? "Sizing"
|
|
2703
|
+
: hasSize
|
|
2704
|
+
? formatStudioBytes(packageSize.sizeBytes)
|
|
2705
|
+
: "Size";
|
|
2706
|
+
const icon = packageSize.loading
|
|
2707
|
+
? "dashicons-update"
|
|
2708
|
+
: packageSize.error
|
|
2709
|
+
? "dashicons-warning"
|
|
2710
|
+
: "dashicons-archive";
|
|
2711
|
+
const className = [
|
|
2712
|
+
"studio-action-button",
|
|
2713
|
+
"studio-compact-button",
|
|
2714
|
+
"studio-package-size-button",
|
|
2715
|
+
packageSize.loading ? "is-loading" : "",
|
|
2716
|
+
packageSize.overLimit ? "is-over-limit" : "",
|
|
2717
|
+
packageSize.stale ? "is-stale" : "",
|
|
2718
|
+
packageSize.error ? "has-error" : ""
|
|
2719
|
+
].filter(Boolean).join(" ");
|
|
2720
|
+
|
|
2721
|
+
return `
|
|
2722
|
+
<button class="${escapeAttr(className)}" type="button" data-action="studio-package-size" id="studio-package-size-button" title="${escapeAttr(studioPackageSizeTitle(packageSize))}" ${packageSize.loading ? "disabled aria-busy=\"true\"" : ""}>
|
|
2723
|
+
<span class="dashicons ${escapeAttr(icon)}" aria-hidden="true"></span>
|
|
2724
|
+
<span>${escapeHtml(label)}</span>
|
|
2725
|
+
</button>
|
|
2726
|
+
`;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
function studioPackageSizeTitle(packageSize) {
|
|
2730
|
+
if (packageSize.loading) {
|
|
2731
|
+
return "Calculating package size";
|
|
2732
|
+
}
|
|
2733
|
+
if (packageSize.error) {
|
|
2734
|
+
return `Package size failed: ${packageSize.error}`;
|
|
2735
|
+
}
|
|
2736
|
+
if (!Number.isFinite(packageSize.sizeBytes)) {
|
|
2737
|
+
return "Calculate package size";
|
|
2738
|
+
}
|
|
2739
|
+
const limit = formatStudioBytes(packageSize.maxSizeBytes || 10 * 1024 * 1024);
|
|
2740
|
+
const status = packageSize.overLimit ? `Over the ${limit} WordPress.org limit` : `Under the ${limit} WordPress.org limit`;
|
|
2741
|
+
const stale = packageSize.stale ? " Stale: recalculate after recent file or ignore changes." : "";
|
|
2742
|
+
const largest = (packageSize.largestFiles ?? []).slice(0, 3);
|
|
2743
|
+
const largestText = largest.length
|
|
2744
|
+
? ` Largest: ${largest.map((file) => `${file.path} (${formatStudioBytes(file.sizeBytes)})`).join(", ")}.`
|
|
2745
|
+
: "";
|
|
2746
|
+
return `${formatStudioBytes(packageSize.sizeBytes)} across ${packageSize.fileCount ?? 0} packaged files. ${status}.${largestText}${stale}`;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
function renderStudioThemeToggle() {
|
|
2750
|
+
const theme = studioTheme();
|
|
2751
|
+
const isLight = theme === "light";
|
|
2752
|
+
const nextTheme = isLight ? "dark" : "light";
|
|
2753
|
+
return `
|
|
2754
|
+
<button class="studio-theme-switch" type="button" role="switch" aria-checked="${isLight ? "true" : "false"}" data-action="studio-toggle-theme" title="${escapeAttr(`Switch to ${nextTheme} mode`)}">
|
|
2755
|
+
<span class="dashicons dashicons-admin-appearance" aria-hidden="true"></span>
|
|
2756
|
+
<span class="studio-theme-switch-track" aria-hidden="true">
|
|
2757
|
+
<span></span>
|
|
2758
|
+
</span>
|
|
2759
|
+
<span class="studio-theme-switch-label">${escapeHtml(isLight ? "Light" : "Dark")}</span>
|
|
2760
|
+
</button>
|
|
2761
|
+
`;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
1882
2764
|
function studioPreviewStateLabel() {
|
|
2765
|
+
if (state.studio.running) {
|
|
2766
|
+
return "Starting";
|
|
2767
|
+
}
|
|
2768
|
+
if (state.studio.playgroundUrl) {
|
|
2769
|
+
return "Ready";
|
|
2770
|
+
}
|
|
2771
|
+
return "Idle";
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
function studioPreviewStateTitle() {
|
|
1883
2775
|
if (state.studio.running) {
|
|
1884
2776
|
return "Starting Playground";
|
|
1885
2777
|
}
|
|
1886
2778
|
if (state.studio.playgroundUrl) {
|
|
1887
2779
|
return "Playground ready";
|
|
1888
2780
|
}
|
|
1889
|
-
return "
|
|
2781
|
+
return "Playground not started";
|
|
1890
2782
|
}
|
|
1891
2783
|
|
|
1892
2784
|
function studioEditorStatusLabel() {
|
|
@@ -1906,10 +2798,62 @@ function switchStudioTab(tab) {
|
|
|
1906
2798
|
if (!["editor", "home", "admin"].includes(tab)) {
|
|
1907
2799
|
return;
|
|
1908
2800
|
}
|
|
1909
|
-
|
|
2801
|
+
const discardingDirtyEditor =
|
|
2802
|
+
state.studio.activeTab === "editor" &&
|
|
2803
|
+
tab !== "editor" &&
|
|
2804
|
+
state.studio.dirty;
|
|
2805
|
+
if (discardingDirtyEditor) {
|
|
2806
|
+
if (!confirm("Discard unsaved changes in the current file?")) {
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
state.studio.draftContent = state.studio.fileContent;
|
|
2810
|
+
state.studio.dirty = false;
|
|
2811
|
+
} else {
|
|
2812
|
+
captureStudioEditorValue();
|
|
2813
|
+
}
|
|
1910
2814
|
state.studio.activeTab = tab;
|
|
1911
2815
|
renderStudio();
|
|
1912
2816
|
remountStudioEditorIfNeeded();
|
|
2817
|
+
updateRouteFromState();
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
async function closeStudioFileTab(relativePath) {
|
|
2821
|
+
if (!relativePath) {
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
const openFiles = Array.isArray(state.studio.openFiles) ? state.studio.openFiles : [];
|
|
2825
|
+
const index = openFiles.indexOf(relativePath);
|
|
2826
|
+
if (index === -1) {
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
const isCurrent = relativePath === state.studio.selectedFile?.path;
|
|
2830
|
+
if (isCurrent && state.studio.dirty && !confirm("Discard unsaved changes in the current file?")) {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
captureStudioEditorValue();
|
|
2835
|
+
const nextOpenFiles = openFiles.filter((path) => path !== relativePath);
|
|
2836
|
+
state.studio.openFiles = nextOpenFiles;
|
|
2837
|
+
|
|
2838
|
+
if (!isCurrent) {
|
|
2839
|
+
renderStudio();
|
|
2840
|
+
remountStudioEditorIfNeeded();
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
state.studio.dirty = false;
|
|
2845
|
+
const nextPath = nextOpenFiles[Math.min(index, nextOpenFiles.length - 1)];
|
|
2846
|
+
if (nextPath) {
|
|
2847
|
+
await selectStudioFile(nextPath, { force: true });
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
state.studio.selectedFile = null;
|
|
2852
|
+
state.studio.fileContent = "";
|
|
2853
|
+
state.studio.draftContent = "";
|
|
2854
|
+
state.studio.activeTab = "home";
|
|
2855
|
+
renderStudio();
|
|
2856
|
+
updateRouteFromState();
|
|
1913
2857
|
}
|
|
1914
2858
|
|
|
1915
2859
|
function toggleStudioTerminal() {
|
|
@@ -1919,22 +2863,166 @@ function toggleStudioTerminal() {
|
|
|
1919
2863
|
remountStudioEditorIfNeeded();
|
|
1920
2864
|
}
|
|
1921
2865
|
|
|
1922
|
-
function
|
|
2866
|
+
function toggleStudioPanel(panel) {
|
|
2867
|
+
if (!["files", "sidebar"].includes(panel)) {
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
captureStudioEditorValue();
|
|
2871
|
+
state.studio.panels = normalizeStudioPanelState(state.studio.panels);
|
|
2872
|
+
state.studio.panels[panel] = !state.studio.panels[panel];
|
|
2873
|
+
saveStudioPanelState(state.studio.panels);
|
|
2874
|
+
renderStudio();
|
|
2875
|
+
remountStudioEditorIfNeeded();
|
|
2876
|
+
requestAnimationFrame(() => state.studio.editor?.layout?.());
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
function openStudioSidebarTab(tab) {
|
|
2880
|
+
captureStudioEditorValue();
|
|
2881
|
+
state.studio.panels = normalizeStudioPanelState(state.studio.panels);
|
|
2882
|
+
state.studio.panels.sidebar = true;
|
|
2883
|
+
saveStudioPanelState(state.studio.panels);
|
|
2884
|
+
setStudioSidebarTab(tab);
|
|
2885
|
+
renderStudio();
|
|
2886
|
+
remountStudioEditorIfNeeded();
|
|
2887
|
+
requestAnimationFrame(() => state.studio.editor?.layout?.());
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
function toggleStudioTheme() {
|
|
2891
|
+
captureStudioEditorValue();
|
|
2892
|
+
state.studio.theme = studioTheme() === "light" ? "dark" : "light";
|
|
2893
|
+
saveStudioTheme(state.studio.theme);
|
|
2894
|
+
renderStudio();
|
|
2895
|
+
remountStudioEditorIfNeeded();
|
|
2896
|
+
requestAnimationFrame(() => state.studio.editor?.layout?.());
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
async function toggleStudioFolder(folderPath) {
|
|
1923
2900
|
if (!folderPath) {
|
|
1924
2901
|
return;
|
|
1925
2902
|
}
|
|
2903
|
+
const normalized = String(folderPath ?? "").replace(/\/+$/, "");
|
|
1926
2904
|
captureStudioEditorValue();
|
|
1927
|
-
|
|
1928
|
-
|
|
2905
|
+
const directory = state.studio.directories?.find((entry) => entry.path === normalized);
|
|
2906
|
+
if (directory?.deferred) {
|
|
2907
|
+
const loaded = await loadStudioDirectory(normalized);
|
|
2908
|
+
if (!loaded) {
|
|
2909
|
+
renderStudio();
|
|
2910
|
+
remountStudioEditorIfNeeded();
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
if (studioFolderIsIgnored(normalized)) {
|
|
2914
|
+
state.studio.expandedIgnoredFolders.add(normalized);
|
|
2915
|
+
} else {
|
|
2916
|
+
state.studio.collapsedFolders.delete(normalized);
|
|
2917
|
+
}
|
|
2918
|
+
renderStudio();
|
|
2919
|
+
remountStudioEditorIfNeeded();
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
if (studioFolderIsIgnored(normalized)) {
|
|
2924
|
+
if (state.studio.expandedIgnoredFolders.has(normalized)) {
|
|
2925
|
+
state.studio.expandedIgnoredFolders.delete(normalized);
|
|
2926
|
+
} else {
|
|
2927
|
+
state.studio.expandedIgnoredFolders.add(normalized);
|
|
2928
|
+
}
|
|
2929
|
+
renderStudio();
|
|
2930
|
+
remountStudioEditorIfNeeded();
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
if (state.studio.collapsedFolders.has(normalized)) {
|
|
2935
|
+
state.studio.collapsedFolders.delete(normalized);
|
|
1929
2936
|
} else {
|
|
1930
|
-
state.studio.collapsedFolders.add(
|
|
2937
|
+
state.studio.collapsedFolders.add(normalized);
|
|
1931
2938
|
}
|
|
1932
2939
|
renderStudio();
|
|
1933
2940
|
remountStudioEditorIfNeeded();
|
|
1934
2941
|
}
|
|
1935
2942
|
|
|
1936
|
-
function
|
|
2943
|
+
async function loadStudioDirectory(folderPath) {
|
|
2944
|
+
const normalized = String(folderPath ?? "").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
2945
|
+
if (!normalized || state.studio.scope !== "local" || !state.studio.id) {
|
|
2946
|
+
return false;
|
|
2947
|
+
}
|
|
2948
|
+
if (state.studio.loadingFolders.has(normalized)) {
|
|
2949
|
+
return false;
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
state.studio.loadingFolders.add(normalized);
|
|
2953
|
+
renderStudio();
|
|
2954
|
+
remountStudioEditorIfNeeded();
|
|
2955
|
+
try {
|
|
2956
|
+
const result = await api(
|
|
2957
|
+
`/api/plugins/local/${encodeURIComponent(state.studio.id)}/files/directory?path=${encodeURIComponent(normalized)}`
|
|
2958
|
+
);
|
|
2959
|
+
mergeStudioExplorerEntries(result.files ?? [], result.directories ?? []);
|
|
2960
|
+
const directory = state.studio.directories.find((entry) => entry.path === normalized);
|
|
2961
|
+
if (directory) {
|
|
2962
|
+
directory.deferred = false;
|
|
2963
|
+
}
|
|
2964
|
+
return true;
|
|
2965
|
+
} catch (error) {
|
|
2966
|
+
appendStudioTerminal(`Folder load failed for ${normalized}: ${error.message}`, "error");
|
|
2967
|
+
notice(error.message, "error");
|
|
2968
|
+
return false;
|
|
2969
|
+
} finally {
|
|
2970
|
+
state.studio.loadingFolders.delete(normalized);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
function mergeStudioExplorerEntries(files, directories) {
|
|
2975
|
+
state.studio.files = mergeStudioEntriesByPath(state.studio.files, files);
|
|
2976
|
+
state.studio.directories = mergeStudioEntriesByPath(state.studio.directories, directories);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
function mergeStudioEntriesByPath(existingEntries = [], incomingEntries = []) {
|
|
2980
|
+
const merged = new Map((existingEntries ?? []).map((entry) => [entry.path, entry]));
|
|
2981
|
+
for (const entry of incomingEntries ?? []) {
|
|
2982
|
+
if (!entry?.path) {
|
|
2983
|
+
continue;
|
|
2984
|
+
}
|
|
2985
|
+
merged.set(entry.path, {
|
|
2986
|
+
...(merged.get(entry.path) ?? {}),
|
|
2987
|
+
...entry
|
|
2988
|
+
});
|
|
2989
|
+
}
|
|
2990
|
+
return Array.from(merged.values());
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
function studioFolderIsIgnored(folderPath) {
|
|
2994
|
+
const normalized = String(folderPath ?? "").replace(/\/+$/, "");
|
|
2995
|
+
if (!normalized) {
|
|
2996
|
+
return false;
|
|
2997
|
+
}
|
|
2998
|
+
const directory = state.studio.directories?.find((entry) => entry.path === normalized);
|
|
2999
|
+
return Boolean(directory?.ignored || studioIgnoredFilesForPrefix(normalized).length);
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
function buildStudioFileTree(files, directories = []) {
|
|
1937
3003
|
const root = { type: "folder", name: "", path: "", children: new Map() };
|
|
3004
|
+
for (const directory of directories) {
|
|
3005
|
+
const parts = directory.path.split("/").filter(Boolean);
|
|
3006
|
+
let node = root;
|
|
3007
|
+
let currentPath = "";
|
|
3008
|
+
parts.forEach((part) => {
|
|
3009
|
+
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
3010
|
+
if (!node.children.has(part)) {
|
|
3011
|
+
node.children.set(part, {
|
|
3012
|
+
type: "folder",
|
|
3013
|
+
name: part,
|
|
3014
|
+
path: currentPath,
|
|
3015
|
+
children: new Map()
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
node = node.children.get(part);
|
|
3019
|
+
});
|
|
3020
|
+
node.deferred = Boolean(directory.deferred);
|
|
3021
|
+
node.ignored = Boolean(directory.ignored);
|
|
3022
|
+
node.ignoredBy = directory.ignoredBy ?? "";
|
|
3023
|
+
node.hardIgnored = Boolean(directory.hardIgnored);
|
|
3024
|
+
}
|
|
3025
|
+
|
|
1938
3026
|
for (const file of files) {
|
|
1939
3027
|
const parts = file.path.split("/").filter(Boolean);
|
|
1940
3028
|
let node = root;
|
|
@@ -1978,19 +3066,48 @@ function renderStudioTreeChildren(children, depth) {
|
|
|
1978
3066
|
|
|
1979
3067
|
function renderStudioTreeNode(node, depth) {
|
|
1980
3068
|
if (node.type === "folder") {
|
|
1981
|
-
const
|
|
3069
|
+
const deferred = Boolean(node.deferred);
|
|
3070
|
+
const loadingFolder = state.studio.loadingFolders?.has(node.path);
|
|
3071
|
+
const nodeIgnored = Boolean(node.ignored);
|
|
1982
3072
|
const containsCurrent = Boolean(
|
|
1983
3073
|
state.studio.selectedFile?.path && state.studio.selectedFile.path.startsWith(`${node.path}/`)
|
|
1984
3074
|
);
|
|
1985
3075
|
const containsAiChange = studioAiChangedFilesForPrefix(node.path).length > 0;
|
|
3076
|
+
const ignoredCount = studioIgnoredFilesForPrefix(node.path).length;
|
|
3077
|
+
const ignoredFolder = nodeIgnored || ignoredCount > 0;
|
|
3078
|
+
const collapsed = deferred ||
|
|
3079
|
+
(ignoredFolder && !state.studio.expandedIgnoredFolders.has(node.path)) ||
|
|
3080
|
+
state.studio.collapsedFolders.has(node.path);
|
|
3081
|
+
const ignorePattern = studioFolderIgnorePattern(node.path);
|
|
3082
|
+
const hasExactIgnore = studioHasIgnorePattern(ignorePattern);
|
|
3083
|
+
const busy = state.studio.ignoreBusyPattern === ignorePattern;
|
|
3084
|
+
const ignoredTitle = node.ignoredBy
|
|
3085
|
+
? `Ignored by ${node.ignoredBy}`
|
|
3086
|
+
: ignoredCount
|
|
3087
|
+
? `${ignoredCount} ignored file${ignoredCount === 1 ? "" : "s"} inside`
|
|
3088
|
+
: "Ignored";
|
|
1986
3089
|
return `
|
|
1987
|
-
<div class="studio-tree-folder${containsCurrent ? " has-current" : ""}${containsAiChange ? " has-ai-changes" : ""}" role="treeitem" aria-expanded="${collapsed ? "false" : "true"}">
|
|
1988
|
-
<
|
|
1989
|
-
<
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
3090
|
+
<div class="studio-tree-folder${containsCurrent ? " has-current" : ""}${containsAiChange ? " has-ai-changes" : ""}${ignoredFolder ? " is-ignored" : ""}" role="treeitem" aria-expanded="${collapsed ? "false" : "true"}">
|
|
3091
|
+
<div class="studio-tree-row studio-tree-folder-row${ignoredFolder ? " has-ignored" : ""}${loadingFolder ? " is-loading" : ""}" style="--depth:${depth}">
|
|
3092
|
+
<button type="button" class="studio-tree-main" data-action="studio-toggle-folder" data-folder="${escapeAttr(node.path)}" ${loadingFolder ? "aria-busy=\"true\"" : ""}>
|
|
3093
|
+
<span class="dashicons ${loadingFolder ? "dashicons-update" : collapsed ? "dashicons-arrow-right-alt2" : "dashicons-arrow-down-alt2"} studio-tree-arrow" aria-hidden="true"></span>
|
|
3094
|
+
<span class="dashicons ${deferred || collapsed ? "dashicons-category" : "dashicons-open-folder"} studio-tree-icon" aria-hidden="true"></span>
|
|
3095
|
+
<span class="studio-tree-label">${escapeHtml(node.name)}</span>
|
|
3096
|
+
</button>
|
|
3097
|
+
<span class="studio-tree-badges">
|
|
3098
|
+
${containsAiChange ? `<span class="studio-tree-ai-badge" title="AI patches inside this folder">AI</span>` : ""}
|
|
3099
|
+
</span>
|
|
3100
|
+
${node.hardIgnored || (nodeIgnored && !hasExactIgnore)
|
|
3101
|
+
? renderStudioReadonlyIgnoreAction(node.ignoredBy ?? ignoredTitle)
|
|
3102
|
+
: renderStudioIgnoreAction({
|
|
3103
|
+
action: hasExactIgnore ? "studio-unignore-rule" : "studio-ignore-folder",
|
|
3104
|
+
label: hasExactIgnore ? "Unignore folder" : "Ignore folder",
|
|
3105
|
+
icon: hasExactIgnore ? "dashicons-hidden" : "dashicons-visibility",
|
|
3106
|
+
path: node.path,
|
|
3107
|
+
pattern: ignorePattern,
|
|
3108
|
+
busy
|
|
3109
|
+
})}
|
|
3110
|
+
</div>
|
|
1994
3111
|
${collapsed ? "" : `<div role="group">${renderStudioTreeChildren(node.children, depth + 1)}</div>`}
|
|
1995
3112
|
</div>
|
|
1996
3113
|
`;
|
|
@@ -1999,6 +3116,12 @@ function renderStudioTreeNode(node, depth) {
|
|
|
1999
3116
|
const current = node.path === state.studio.selectedFile?.path;
|
|
2000
3117
|
const checkCounts = studioCheckCountsForPath(node.path);
|
|
2001
3118
|
const aiChange = studioAiChangedFile(node.path);
|
|
3119
|
+
const ignored = Boolean(node.file?.ignored);
|
|
3120
|
+
const ignoredBy = node.file?.ignoredBy;
|
|
3121
|
+
const hardIgnored = Boolean(node.file?.hardIgnored);
|
|
3122
|
+
const exactPattern = studioFileIgnorePattern(node.path);
|
|
3123
|
+
const hasExactIgnore = studioHasIgnorePattern(exactPattern);
|
|
3124
|
+
const busy = state.studio.ignoreBusyPattern === exactPattern;
|
|
2002
3125
|
const checkBadge = checkCounts.total
|
|
2003
3126
|
? `<span class="studio-tree-check-badge${checkCounts.error ? " has-errors" : ""}" title="${escapeAttr(formatCheckCounts(checkCounts))}">${escapeHtml(String(checkCounts.total))}</span>`
|
|
2004
3127
|
: "";
|
|
@@ -2006,11 +3129,43 @@ function renderStudioTreeNode(node, depth) {
|
|
|
2006
3129
|
? `<span class="studio-tree-ai-badge" title="${escapeAttr(`AI proposed ${aiChange.status} patch`)}">AI</span>`
|
|
2007
3130
|
: "";
|
|
2008
3131
|
return `
|
|
2009
|
-
<
|
|
2010
|
-
<
|
|
2011
|
-
|
|
2012
|
-
|
|
3132
|
+
<div role="treeitem" class="studio-tree-row studio-tree-file-row${current ? " is-current" : ""}${checkCounts.error ? " has-check-errors" : ""}${aiChange ? " has-ai-changes" : ""}${ignored ? " is-ignored" : ""}" style="--depth:${depth}">
|
|
3133
|
+
<button type="button" class="studio-tree-main" data-action="studio-file" data-path="${escapeAttr(node.path)}">
|
|
3134
|
+
<span class="studio-tree-indent" aria-hidden="true"></span>
|
|
3135
|
+
<span class="dashicons ${studioFileIcon(node.path)} studio-tree-icon" aria-hidden="true"></span>
|
|
3136
|
+
<span class="studio-tree-label">${escapeHtml(node.name)}</span>
|
|
3137
|
+
</button>
|
|
2013
3138
|
<span class="studio-tree-badges">${aiBadge}${checkBadge}</span>
|
|
3139
|
+
${hardIgnored || (ignored && !hasExactIgnore)
|
|
3140
|
+
? renderStudioReadonlyIgnoreAction(ignoredBy ?? "Pressship package rules")
|
|
3141
|
+
: renderStudioIgnoreAction({
|
|
3142
|
+
action: hasExactIgnore ? "studio-unignore-rule" : "studio-ignore-file",
|
|
3143
|
+
label: hasExactIgnore ? "Unignore file" : "Ignore file",
|
|
3144
|
+
icon: hasExactIgnore ? "dashicons-hidden" : "dashicons-visibility",
|
|
3145
|
+
path: node.path,
|
|
3146
|
+
pattern: exactPattern,
|
|
3147
|
+
busy
|
|
3148
|
+
})}
|
|
3149
|
+
</div>
|
|
3150
|
+
`;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
function renderStudioReadonlyIgnoreAction(reason) {
|
|
3154
|
+
const title = reason ? `Ignored by ${reason}` : "Ignored";
|
|
3155
|
+
return `
|
|
3156
|
+
<span class="studio-tree-action is-readonly" title="${escapeAttr(title)}" aria-label="${escapeAttr(title)}">
|
|
3157
|
+
<span class="dashicons dashicons-hidden" aria-hidden="true"></span>
|
|
3158
|
+
</span>
|
|
3159
|
+
`;
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
function renderStudioIgnoreAction({ action, label, icon, path, pattern, busy }) {
|
|
3163
|
+
if (!pattern || path === ".pressshipignore" || state.studio.scope !== "local") {
|
|
3164
|
+
return "";
|
|
3165
|
+
}
|
|
3166
|
+
return `
|
|
3167
|
+
<button type="button" class="studio-tree-action${busy ? " is-busy" : ""}" data-action="${escapeAttr(action)}" data-path="${escapeAttr(path ?? "")}" data-pattern="${escapeAttr(pattern)}" title="${escapeAttr(label)}" aria-label="${escapeAttr(label)}" ${busy ? "disabled aria-busy=\"true\"" : ""}>
|
|
3168
|
+
<span class="dashicons ${busy ? "dashicons-update" : icon}" aria-hidden="true"></span>
|
|
2014
3169
|
</button>
|
|
2015
3170
|
`;
|
|
2016
3171
|
}
|
|
@@ -2027,13 +3182,19 @@ function renderStudioAiSidebar() {
|
|
|
2027
3182
|
|
|
2028
3183
|
function renderStudioSidebarTabs(activeTab) {
|
|
2029
3184
|
return `
|
|
3185
|
+
<header class="studio-secondary-header">
|
|
3186
|
+
<strong>${activeTab === "release" ? "Release" : "AI Helper"}</strong>
|
|
3187
|
+
<button class="studio-pane-action" type="button" data-action="studio-toggle-sidebar" aria-label="Hide Secondary Side Bar" title="Hide Secondary Side Bar">
|
|
3188
|
+
<span class="dashicons dashicons-no-alt" aria-hidden="true"></span>
|
|
3189
|
+
</button>
|
|
3190
|
+
</header>
|
|
2030
3191
|
<div class="studio-sidebar-tabs ps-segmented" role="tablist" aria-label="Studio sidebar">
|
|
2031
3192
|
<button class="ps-segmented-option${activeTab === "ai" ? " is-active" : ""}" type="button" role="tab" aria-selected="${activeTab === "ai"}" data-action="studio-sidebar-tab" data-tab="ai">
|
|
2032
3193
|
<span class="dashicons dashicons-format-chat" aria-hidden="true"></span>
|
|
2033
3194
|
AI Helper
|
|
2034
3195
|
</button>
|
|
2035
3196
|
<button class="ps-segmented-option${activeTab === "release" ? " is-active" : ""}" type="button" role="tab" aria-selected="${activeTab === "release"}" data-action="studio-sidebar-tab" data-tab="release">
|
|
2036
|
-
<span class="dashicons
|
|
3197
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
2037
3198
|
Release
|
|
2038
3199
|
</button>
|
|
2039
3200
|
</div>
|
|
@@ -2245,7 +3406,7 @@ function renderStudioAiMessages() {
|
|
|
2245
3406
|
<span>${escapeHtml(aiMessageRoleLabel(message))}</span>
|
|
2246
3407
|
<time>${escapeHtml(formatTime(message.createdAt))}</time>
|
|
2247
3408
|
</header>
|
|
2248
|
-
<
|
|
3409
|
+
<div class="studio-ai-markdown">${renderStudioAiMarkdown(message.text)}</div>
|
|
2249
3410
|
</div>
|
|
2250
3411
|
</article>
|
|
2251
3412
|
`
|
|
@@ -2623,69 +3784,479 @@ function appendStudioTerminal(message, tone = "muted") {
|
|
|
2623
3784
|
}
|
|
2624
3785
|
}
|
|
2625
3786
|
|
|
2626
|
-
function
|
|
2627
|
-
|
|
3787
|
+
function appendStudioCliCommand(command) {
|
|
3788
|
+
if (state.activeView !== "studio" || !command || !Array.isArray(state.studio.terminal)) {
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
state.studio.terminalOpen = true;
|
|
3792
|
+
appendStudioTerminal(`$ ${command}`, "command");
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
function quoteCliArg(value) {
|
|
3796
|
+
const text = String(value ?? "").trim();
|
|
3797
|
+
if (!text) {
|
|
3798
|
+
return "''";
|
|
3799
|
+
}
|
|
3800
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(text)
|
|
3801
|
+
? text
|
|
3802
|
+
: `'${text.replace(/'/g, "'\\''")}'`;
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
function studioCliCommand(parts) {
|
|
3806
|
+
return [STUDIO_CLI_PREFIX, ...parts].filter(Boolean).join(" ");
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
function studioOpenCliCommand() {
|
|
3810
|
+
const parts = ["studio"];
|
|
3811
|
+
const host = window.location.hostname;
|
|
3812
|
+
const port = window.location.port;
|
|
3813
|
+
if (host && !["127.0.0.1", "localhost"].includes(host)) {
|
|
3814
|
+
parts.push("--host", quoteCliArg(host));
|
|
3815
|
+
}
|
|
3816
|
+
if (port && port !== "9477") {
|
|
3817
|
+
parts.push("--port", quoteCliArg(port));
|
|
3818
|
+
}
|
|
3819
|
+
return studioCliCommand(parts);
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
function localPluginForCli(localId = state.studio.id) {
|
|
3823
|
+
return state.local.find((plugin) => plugin.id === localId) ??
|
|
3824
|
+
(state.studio.scope === "local" && state.studio.id === localId ? state.studio.plugin : null);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
function localPluginCliTarget(localId = state.studio.id) {
|
|
3828
|
+
const plugin = localPluginForCli(localId);
|
|
3829
|
+
return quoteCliArg(plugin?.path || plugin?.slug || localId || ".");
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
function studioCliIgnoreFlags(localId = state.studio.id) {
|
|
3833
|
+
if (localId !== state.studio.id) {
|
|
3834
|
+
return [];
|
|
3835
|
+
}
|
|
3836
|
+
return studioIgnorePatterns().flatMap((pattern) => ["--ignore", quoteCliArg(pattern)]);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
function studioPublishCliCommand(localId, action, options = {}) {
|
|
3840
|
+
const normalizedAction = ["submit", "release"].includes(action) ? action : "auto";
|
|
3841
|
+
const parts = ["publish", localPluginCliTarget(localId)];
|
|
3842
|
+
if (normalizedAction !== "auto") {
|
|
3843
|
+
parts.push(`--${normalizedAction}`);
|
|
3844
|
+
}
|
|
3845
|
+
if (options.dryRun) {
|
|
3846
|
+
parts.push("--dry-run");
|
|
3847
|
+
}
|
|
3848
|
+
parts.push(...studioCliIgnoreFlags(localId), "--yes");
|
|
3849
|
+
return studioCliCommand(parts);
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
function studioPlaygroundCliCommand(input) {
|
|
3853
|
+
const target = input.scope === "local" ? localPluginCliTarget(input.id) : quoteCliArg(input.id);
|
|
3854
|
+
const settings = state.settings ?? {};
|
|
3855
|
+
const parts = ["demo", target, "--reset", "--skip-browser"];
|
|
3856
|
+
if (input.wpVersion && input.wpVersion !== "latest") {
|
|
3857
|
+
parts.push("--wp", quoteCliArg(input.wpVersion));
|
|
3858
|
+
}
|
|
3859
|
+
if (settings.playgroundDatabaseMode && settings.playgroundDatabaseMode !== "auto") {
|
|
3860
|
+
parts.push("--database", quoteCliArg(settings.playgroundDatabaseMode));
|
|
3861
|
+
}
|
|
3862
|
+
if (settings.playgroundDatabaseMode === "mysql") {
|
|
3863
|
+
parts.push(
|
|
3864
|
+
"--mysql-host",
|
|
3865
|
+
quoteCliArg(settings.playgroundMysqlHost ?? "127.0.0.1"),
|
|
3866
|
+
"--mysql-port",
|
|
3867
|
+
quoteCliArg(settings.playgroundMysqlPort ?? 3306),
|
|
3868
|
+
"--mysql-user",
|
|
3869
|
+
quoteCliArg(settings.playgroundMysqlUser ?? "root"),
|
|
3870
|
+
"--mysql-database-prefix",
|
|
3871
|
+
quoteCliArg(settings.playgroundMysqlDatabasePrefix ?? "pressship_playground")
|
|
3872
|
+
);
|
|
3873
|
+
if (settings.playgroundMysqlPassword) {
|
|
3874
|
+
parts.push("--mysql-password", quoteCliArg(settings.playgroundMysqlPassword));
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
return studioCliCommand(parts);
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
function studioCliCommandForJob(input) {
|
|
3881
|
+
if (state.activeView !== "studio") {
|
|
3882
|
+
return "";
|
|
3883
|
+
}
|
|
3884
|
+
switch (input?.type) {
|
|
3885
|
+
case "clone": {
|
|
3886
|
+
const parts = ["get", quoteCliArg(input.slug)];
|
|
3887
|
+
if (input.destination) {
|
|
3888
|
+
parts.push(quoteCliArg(input.destination));
|
|
3889
|
+
}
|
|
3890
|
+
return studioCliCommand(parts);
|
|
3891
|
+
}
|
|
3892
|
+
case "play":
|
|
3893
|
+
return studioPlaygroundCliCommand(input);
|
|
3894
|
+
case "check":
|
|
3895
|
+
return studioCliCommand([
|
|
3896
|
+
"verify",
|
|
3897
|
+
localPluginCliTarget(input.localId),
|
|
3898
|
+
"--skip-readme-validator",
|
|
3899
|
+
...studioCliIgnoreFlags(input.localId)
|
|
3900
|
+
]);
|
|
3901
|
+
case "dry-run-publish":
|
|
3902
|
+
return studioPublishCliCommand(input.localId, input.action, { dryRun: true });
|
|
3903
|
+
case "confirm-publish": {
|
|
3904
|
+
const dryRun = state.studio.release?.dryRun;
|
|
3905
|
+
if (!dryRun?.approvalId || dryRun.approvalId !== input.approvalId) {
|
|
3906
|
+
return "";
|
|
3907
|
+
}
|
|
3908
|
+
const action = dryRun?.route?.action ?? "auto";
|
|
3909
|
+
return state.studio.id ? studioPublishCliCommand(state.studio.id, action) : "";
|
|
3910
|
+
}
|
|
3911
|
+
default:
|
|
3912
|
+
return "";
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
function selectedStudioAiAssistant() {
|
|
3917
|
+
return state.settings?.aiAssistant ?? "none";
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
function assistantLabel(id) {
|
|
3921
|
+
const provider = aiAssistanceProviders().find((item) => item.id === id);
|
|
3922
|
+
if (provider) {
|
|
3923
|
+
return provider.label;
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
const labels = {
|
|
3927
|
+
none: "AI",
|
|
3928
|
+
claude: "Claude Code",
|
|
3929
|
+
codex: "Codex CLI",
|
|
3930
|
+
copilot: "GitHub Copilot CLI",
|
|
3931
|
+
cursor: "Cursor",
|
|
3932
|
+
gemini: "Gemini CLI",
|
|
3933
|
+
opencode: "OpenCode",
|
|
3934
|
+
"wp-studio": "WP Studio"
|
|
3935
|
+
};
|
|
3936
|
+
return labels[id] ?? capitalize(String(id));
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
function aiMessageRoleLabel(message) {
|
|
3940
|
+
if (message.role === "user") return "You";
|
|
3941
|
+
if (message.role === "assistant") return assistantLabel(message.assistant ?? state.studio.aiActiveAssistant ?? selectedStudioAiAssistant());
|
|
3942
|
+
return "Studio";
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
function canRunStudioAi() {
|
|
3946
|
+
return (
|
|
3947
|
+
state.studio.scope === "local" &&
|
|
3948
|
+
Boolean(state.studio.id) &&
|
|
3949
|
+
!state.studio.loading &&
|
|
3950
|
+
!state.studio.aiRunning &&
|
|
3951
|
+
selectedStudioAiAssistant() !== "none"
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
function studioAiDisabledReason() {
|
|
3956
|
+
if (state.studio.scope !== "local") {
|
|
3957
|
+
return "Open a local plugin to use AI.";
|
|
3958
|
+
}
|
|
3959
|
+
if (selectedStudioAiAssistant() === "none") {
|
|
3960
|
+
return "Select AI Assistance in Settings.";
|
|
3961
|
+
}
|
|
3962
|
+
if (state.studio.aiRunning) {
|
|
3963
|
+
return "";
|
|
3964
|
+
}
|
|
3965
|
+
return "";
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
function applyStudioCheckState(checkState) {
|
|
3969
|
+
if (!checkState) {
|
|
3970
|
+
state.studio.checkFindings = [];
|
|
3971
|
+
state.studio.checkSummary = null;
|
|
3972
|
+
state.studio.checkRanAt = null;
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
state.studio.checkFindings = checkState.findings ?? [];
|
|
3977
|
+
state.studio.checkSummary = checkState.summary ?? null;
|
|
3978
|
+
state.studio.checkRanAt = checkState.checkedAt ?? null;
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
function applyStudioIgnoreState(ignoreState) {
|
|
3982
|
+
state.studio.ignoreState = {
|
|
3983
|
+
...createInitialStudioIgnoreState(),
|
|
3984
|
+
...(ignoreState ?? {})
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
function applyStudioPackageSize(packageSize) {
|
|
3989
|
+
if (packageSize?.status === "calculating") {
|
|
3990
|
+
state.studio.packageSize = {
|
|
3991
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
3992
|
+
loading: true,
|
|
3993
|
+
error: "",
|
|
3994
|
+
stale: false
|
|
3995
|
+
};
|
|
3996
|
+
scheduleStudioPackageSizePoll();
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
if (packageSize?.status === "error") {
|
|
4000
|
+
state.studio.packageSize = {
|
|
4001
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
4002
|
+
loading: false,
|
|
4003
|
+
error: packageSize.error ?? "Package size could not be calculated.",
|
|
4004
|
+
calculatedAt: packageSize.calculatedAt ?? new Date().toISOString(),
|
|
4005
|
+
stale: false
|
|
4006
|
+
};
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
state.studio.packageSize = {
|
|
4011
|
+
...createInitialStudioPackageSize(),
|
|
4012
|
+
...(packageSize ?? {}),
|
|
4013
|
+
loading: false,
|
|
4014
|
+
error: "",
|
|
4015
|
+
calculatedAt: packageSize?.calculatedAt ?? new Date().toISOString(),
|
|
4016
|
+
stale: false
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
function markStudioPackageSizeStale() {
|
|
4021
|
+
const packageSize = state.studio.packageSize;
|
|
4022
|
+
if (!packageSize || !packageSize.calculatedAt) {
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
state.studio.packageSize = {
|
|
4026
|
+
...packageSize,
|
|
4027
|
+
stale: true
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
async function refreshStudioPackageSize(options = {}) {
|
|
4032
|
+
if (state.studio.scope !== "local" || !state.studio.id) {
|
|
4033
|
+
return;
|
|
4034
|
+
}
|
|
4035
|
+
if (state.studio.packageSize?.loading && !options.poll) {
|
|
4036
|
+
return;
|
|
4037
|
+
}
|
|
4038
|
+
if (!options.force && state.studio.packageSize?.calculatedAt) {
|
|
4039
|
+
return;
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
const localId = state.studio.id;
|
|
4043
|
+
if (options.notify) {
|
|
4044
|
+
appendStudioCliCommand(studioCliCommand([
|
|
4045
|
+
"pack",
|
|
4046
|
+
localPluginCliTarget(localId),
|
|
4047
|
+
"--no-verify",
|
|
4048
|
+
...studioCliIgnoreFlags(localId),
|
|
4049
|
+
"--json"
|
|
4050
|
+
]));
|
|
4051
|
+
}
|
|
4052
|
+
state.studio.packageSize = {
|
|
4053
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
4054
|
+
loading: true,
|
|
4055
|
+
error: ""
|
|
4056
|
+
};
|
|
4057
|
+
if (options.render !== false) {
|
|
4058
|
+
renderStudio();
|
|
4059
|
+
remountStudioEditorIfNeeded();
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
try {
|
|
4063
|
+
const packageSize = await api(`/api/plugins/local/${encodeURIComponent(localId)}/package-size`);
|
|
4064
|
+
if (state.studio.id !== localId) {
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
applyStudioPackageSize(packageSize);
|
|
4068
|
+
if (options.notify && packageSize.status !== "calculating") {
|
|
4069
|
+
if (packageSize.status === "error") {
|
|
4070
|
+
notice(packageSize.error ?? "Package size could not be calculated.", "error");
|
|
4071
|
+
} else {
|
|
4072
|
+
notice(
|
|
4073
|
+
packageSize.overLimit
|
|
4074
|
+
? `Package is ${formatStudioBytes(packageSize.sizeBytes)}, over the WordPress.org 10 MB limit.`
|
|
4075
|
+
: `Package is ${formatStudioBytes(packageSize.sizeBytes)}.`,
|
|
4076
|
+
packageSize.overLimit ? "warning" : "success"
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
} catch (error) {
|
|
4081
|
+
if (state.studio.id !== localId) {
|
|
4082
|
+
return;
|
|
4083
|
+
}
|
|
4084
|
+
state.studio.packageSize = {
|
|
4085
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
4086
|
+
loading: false,
|
|
4087
|
+
error: error.message
|
|
4088
|
+
};
|
|
4089
|
+
if (options.notify) {
|
|
4090
|
+
notice(error.message, "error");
|
|
4091
|
+
}
|
|
4092
|
+
} finally {
|
|
4093
|
+
if (state.studio.id === localId && options.render !== false) {
|
|
4094
|
+
renderStudio();
|
|
4095
|
+
remountStudioEditorIfNeeded();
|
|
4096
|
+
updateStudioControls();
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
function scheduleStudioPackageSizePoll() {
|
|
4102
|
+
if (studioPackageSizePollTimer) {
|
|
4103
|
+
return;
|
|
4104
|
+
}
|
|
4105
|
+
studioPackageSizePollTimer = window.setTimeout(() => {
|
|
4106
|
+
studioPackageSizePollTimer = null;
|
|
4107
|
+
void refreshStudioPackageSize({ force: true, render: true, poll: true });
|
|
4108
|
+
}, 1200);
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
async function refreshStudioIgnoreState(options = {}) {
|
|
4112
|
+
if (state.studio.scope !== "local" || !state.studio.id) {
|
|
4113
|
+
return;
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
const localId = state.studio.id;
|
|
4117
|
+
state.studio.ignoreLoading = true;
|
|
4118
|
+
state.studio.ignoreError = "";
|
|
4119
|
+
if (options.render !== false) {
|
|
4120
|
+
updateStudioSidebar();
|
|
4121
|
+
}
|
|
4122
|
+
try {
|
|
4123
|
+
const requests = [
|
|
4124
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/ignore-state`)
|
|
4125
|
+
];
|
|
4126
|
+
if (options.files) {
|
|
4127
|
+
requests.push(api(`/api/plugins/local/${encodeURIComponent(localId)}/files`));
|
|
4128
|
+
}
|
|
4129
|
+
const [ignoreState, filesResult] = await Promise.all(requests);
|
|
4130
|
+
applyStudioIgnoreState(ignoreState);
|
|
4131
|
+
if (filesResult) {
|
|
4132
|
+
state.studio.files = filesResult.files ?? state.studio.files;
|
|
4133
|
+
state.studio.directories = filesResult.directories ?? state.studio.directories;
|
|
4134
|
+
}
|
|
4135
|
+
} catch (error) {
|
|
4136
|
+
state.studio.ignoreError = error.message;
|
|
4137
|
+
} finally {
|
|
4138
|
+
state.studio.ignoreLoading = false;
|
|
4139
|
+
state.studio.ignoreBusyPattern = "";
|
|
4140
|
+
state.studio.ignoreBusyPath = "";
|
|
4141
|
+
if (options.render !== false) {
|
|
4142
|
+
renderStudio();
|
|
4143
|
+
remountStudioEditorIfNeeded();
|
|
4144
|
+
updateStudioSidebar();
|
|
4145
|
+
updateStudioControls();
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
function studioIgnorePatterns() {
|
|
4151
|
+
return state.studio.ignoreState?.patterns ?? [];
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
function studioHasIgnorePattern(pattern) {
|
|
4155
|
+
return studioIgnorePatterns().includes(pattern);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
function studioFileIgnorePattern(filePath) {
|
|
4159
|
+
return String(filePath ?? "").replace(/^\.\/+/, "");
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
function studioFolderIgnorePattern(folderPath) {
|
|
4163
|
+
const normalized = String(folderPath ?? "").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
4164
|
+
return normalized ? `${normalized}/**` : "";
|
|
2628
4165
|
}
|
|
2629
4166
|
|
|
2630
|
-
function
|
|
2631
|
-
const
|
|
2632
|
-
if (
|
|
2633
|
-
return
|
|
4167
|
+
function studioIgnoredFilesForPrefix(prefix) {
|
|
4168
|
+
const normalized = String(prefix ?? "").replace(/\/+$/, "");
|
|
4169
|
+
if (!normalized) {
|
|
4170
|
+
return [];
|
|
2634
4171
|
}
|
|
2635
4172
|
|
|
2636
|
-
const
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
4173
|
+
const files = [];
|
|
4174
|
+
const seen = new Set();
|
|
4175
|
+
const addFile = (file) => {
|
|
4176
|
+
const filePath = String(file?.path ?? "");
|
|
4177
|
+
if (!filePath || seen.has(filePath)) {
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
4180
|
+
if (filePath === normalized || filePath.startsWith(`${normalized}/`)) {
|
|
4181
|
+
seen.add(filePath);
|
|
4182
|
+
files.push(file);
|
|
4183
|
+
}
|
|
2645
4184
|
};
|
|
2646
|
-
return labels[id] ?? capitalize(String(id));
|
|
2647
|
-
}
|
|
2648
4185
|
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
4186
|
+
for (const file of state.studio.ignoreState?.ignoredFiles ?? []) {
|
|
4187
|
+
addFile(file);
|
|
4188
|
+
}
|
|
4189
|
+
for (const file of state.studio.files ?? []) {
|
|
4190
|
+
if (file?.ignored) {
|
|
4191
|
+
addFile(file);
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
2654
4194
|
|
|
2655
|
-
|
|
2656
|
-
return (
|
|
2657
|
-
state.studio.scope === "local" &&
|
|
2658
|
-
Boolean(state.studio.id) &&
|
|
2659
|
-
!state.studio.loading &&
|
|
2660
|
-
!state.studio.aiRunning &&
|
|
2661
|
-
selectedStudioAiAssistant() !== "none"
|
|
2662
|
-
);
|
|
4195
|
+
return files;
|
|
2663
4196
|
}
|
|
2664
4197
|
|
|
2665
|
-
function
|
|
2666
|
-
if (state.studio.scope !== "local") {
|
|
2667
|
-
return
|
|
2668
|
-
}
|
|
2669
|
-
if (selectedStudioAiAssistant() === "none") {
|
|
2670
|
-
return "Select AI Assistance in Settings.";
|
|
4198
|
+
async function addStudioIgnoreRule(pattern, options = {}) {
|
|
4199
|
+
if (!pattern || state.studio.scope !== "local" || !state.studio.id) {
|
|
4200
|
+
return;
|
|
2671
4201
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
4202
|
+
|
|
4203
|
+
state.studio.ignoreBusyPattern = pattern;
|
|
4204
|
+
state.studio.ignoreBusyPath = options.path ?? "";
|
|
4205
|
+
updateStudioSidebar();
|
|
4206
|
+
try {
|
|
4207
|
+
const ignoreState = await api(`/api/plugins/local/${encodeURIComponent(state.studio.id)}/ignore-rules`, {
|
|
4208
|
+
method: "POST",
|
|
4209
|
+
body: { pattern }
|
|
4210
|
+
});
|
|
4211
|
+
applyStudioIgnoreState(ignoreState);
|
|
4212
|
+
markStudioPackageSizeStale();
|
|
4213
|
+
await refreshStudioIgnoreState({ files: true, render: false });
|
|
4214
|
+
if (state.studio.selectedFile?.path === ".pressshipignore") {
|
|
4215
|
+
await syncSelectedStudioFileFromDisk({ reason: "ignore-rule", reportErrors: true });
|
|
4216
|
+
}
|
|
4217
|
+
appendStudioTerminal(`Ignored ${options.label ?? pattern}.`, "success");
|
|
4218
|
+
} catch (error) {
|
|
4219
|
+
appendStudioTerminal(error.message, "error");
|
|
4220
|
+
notice(error.message, "error");
|
|
4221
|
+
} finally {
|
|
4222
|
+
state.studio.ignoreBusyPattern = "";
|
|
4223
|
+
state.studio.ignoreBusyPath = "";
|
|
4224
|
+
renderStudio();
|
|
4225
|
+
remountStudioEditorIfNeeded();
|
|
4226
|
+
updateStudioSidebar();
|
|
4227
|
+
updateStudioControls();
|
|
2674
4228
|
}
|
|
2675
|
-
return "";
|
|
2676
4229
|
}
|
|
2677
4230
|
|
|
2678
|
-
function
|
|
2679
|
-
if (!
|
|
2680
|
-
state.studio.checkFindings = [];
|
|
2681
|
-
state.studio.checkSummary = null;
|
|
2682
|
-
state.studio.checkRanAt = null;
|
|
4231
|
+
async function removeStudioIgnoreRule(pattern) {
|
|
4232
|
+
if (!pattern || state.studio.scope !== "local" || !state.studio.id) {
|
|
2683
4233
|
return;
|
|
2684
4234
|
}
|
|
2685
4235
|
|
|
2686
|
-
state.studio.
|
|
2687
|
-
|
|
2688
|
-
|
|
4236
|
+
state.studio.ignoreBusyPattern = pattern;
|
|
4237
|
+
updateStudioSidebar();
|
|
4238
|
+
try {
|
|
4239
|
+
const ignoreState = await api(`/api/plugins/local/${encodeURIComponent(state.studio.id)}/ignore-rules`, {
|
|
4240
|
+
method: "DELETE",
|
|
4241
|
+
body: { pattern }
|
|
4242
|
+
});
|
|
4243
|
+
applyStudioIgnoreState(ignoreState);
|
|
4244
|
+
markStudioPackageSizeStale();
|
|
4245
|
+
await refreshStudioIgnoreState({ files: true, render: false });
|
|
4246
|
+
if (state.studio.selectedFile?.path === ".pressshipignore") {
|
|
4247
|
+
await syncSelectedStudioFileFromDisk({ reason: "ignore-rule", reportErrors: true });
|
|
4248
|
+
}
|
|
4249
|
+
appendStudioTerminal(`Removed ignore rule ${pattern}.`, "success");
|
|
4250
|
+
} catch (error) {
|
|
4251
|
+
appendStudioTerminal(error.message, "error");
|
|
4252
|
+
notice(error.message, "error");
|
|
4253
|
+
} finally {
|
|
4254
|
+
state.studio.ignoreBusyPattern = "";
|
|
4255
|
+
renderStudio();
|
|
4256
|
+
remountStudioEditorIfNeeded();
|
|
4257
|
+
updateStudioSidebar();
|
|
4258
|
+
updateStudioControls();
|
|
4259
|
+
}
|
|
2689
4260
|
}
|
|
2690
4261
|
|
|
2691
4262
|
function appendStudioAiMessage(role, text, tone = "muted") {
|
|
@@ -2711,7 +4282,7 @@ function appendStudioAiOutput(text, tone = "log") {
|
|
|
2711
4282
|
}
|
|
2712
4283
|
last.text = `${last.text}${output}`;
|
|
2713
4284
|
last.tone = tone === "error" ? "error" : last.tone === "status" ? "log" : last.tone;
|
|
2714
|
-
|
|
4285
|
+
updateStudioAiMessageList();
|
|
2715
4286
|
}
|
|
2716
4287
|
|
|
2717
4288
|
function updateStudioAiSidebar() {
|
|
@@ -2723,11 +4294,40 @@ function updateStudioAiSidebar() {
|
|
|
2723
4294
|
node.innerHTML = renderStudioAiSidebar();
|
|
2724
4295
|
const messages = document.getElementById("studio-ai-messages");
|
|
2725
4296
|
if (messages) {
|
|
2726
|
-
messages
|
|
4297
|
+
scrollStudioAiMessagesToBottom(messages);
|
|
4298
|
+
}
|
|
4299
|
+
updateStudioAiControls();
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4302
|
+
function updateStudioAiMessageList(options = {}) {
|
|
4303
|
+
const messages = document.getElementById("studio-ai-messages");
|
|
4304
|
+
if (!messages) {
|
|
4305
|
+
updateStudioAiSidebar();
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4309
|
+
const shouldStick = options.forceScroll || isStudioAiMessagesNearBottom(messages);
|
|
4310
|
+
messages.innerHTML = renderStudioAiMessages();
|
|
4311
|
+
if (shouldStick) {
|
|
4312
|
+
scrollStudioAiMessagesToBottom(messages);
|
|
2727
4313
|
}
|
|
2728
4314
|
updateStudioAiControls();
|
|
2729
4315
|
}
|
|
2730
4316
|
|
|
4317
|
+
function isStudioAiMessagesNearBottom(messages) {
|
|
4318
|
+
return messages.scrollHeight - messages.scrollTop - messages.clientHeight < 80;
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
function scrollStudioAiMessagesToBottom(messages) {
|
|
4322
|
+
const previousScrollBehavior = messages.style.scrollBehavior;
|
|
4323
|
+
messages.style.scrollBehavior = "auto";
|
|
4324
|
+
messages.scrollTop = messages.scrollHeight;
|
|
4325
|
+
requestAnimationFrame(() => {
|
|
4326
|
+
messages.scrollTop = messages.scrollHeight;
|
|
4327
|
+
messages.style.scrollBehavior = previousScrollBehavior;
|
|
4328
|
+
});
|
|
4329
|
+
}
|
|
4330
|
+
|
|
2731
4331
|
function updateStudioAiControls() {
|
|
2732
4332
|
const prompt = document.getElementById("studio-ai-prompt");
|
|
2733
4333
|
const send = document.getElementById("studio-ai-send-button");
|
|
@@ -2994,6 +4594,16 @@ function chooseInitialStudioFile(files, slug) {
|
|
|
2994
4594
|
);
|
|
2995
4595
|
}
|
|
2996
4596
|
|
|
4597
|
+
function canSaveStudioFile() {
|
|
4598
|
+
return Boolean(
|
|
4599
|
+
state.studio.scope === "local" &&
|
|
4600
|
+
state.studio.id &&
|
|
4601
|
+
!state.studio.readOnly &&
|
|
4602
|
+
state.studio.selectedFile &&
|
|
4603
|
+
state.studio.dirty
|
|
4604
|
+
);
|
|
4605
|
+
}
|
|
4606
|
+
|
|
2997
4607
|
function updateStudioControls() {
|
|
2998
4608
|
const canRun = Boolean(state.studio.id) && !state.studio.loading && !state.studio.running;
|
|
2999
4609
|
const canCheck =
|
|
@@ -3015,12 +4625,17 @@ function updateStudioControls() {
|
|
|
3015
4625
|
}
|
|
3016
4626
|
if (studioCheck) {
|
|
3017
4627
|
studioCheck.disabled = !canCheck;
|
|
4628
|
+
studioCheck.setAttribute("aria-label", state.studio.checkSummary ? "Re-run Plugin Check" : "Run Plugin Check");
|
|
4629
|
+
studioCheck.title = state.studio.checkSummary ? "Re-run Plugin Check" : "Run Plugin Check";
|
|
3018
4630
|
studioCheck.innerHTML = state.studio.checking
|
|
3019
|
-
? `<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
3020
|
-
: `<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span
|
|
4631
|
+
? `<span class="dashicons dashicons-update" aria-hidden="true"></span><span>Checking</span>`
|
|
4632
|
+
: `<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><span>${state.studio.checkSummary ? "Re-check" : "Check"}</span>`;
|
|
3021
4633
|
}
|
|
3022
4634
|
if (studioSave) {
|
|
3023
|
-
studioSave.disabled =
|
|
4635
|
+
studioSave.disabled = !canSaveStudioFile();
|
|
4636
|
+
studioSave.title = `Save ${studioSaveShortcutLabel()}`;
|
|
4637
|
+
studioSave.setAttribute("aria-label", `Save ${studioSaveShortcutLabel()}`);
|
|
4638
|
+
studioSave.setAttribute("aria-keyshortcuts", studioSaveAriaShortcut());
|
|
3024
4639
|
}
|
|
3025
4640
|
updateStudioAiControls();
|
|
3026
4641
|
}
|
|
@@ -3078,7 +4693,7 @@ async function mountStudioEditor(content) {
|
|
|
3078
4693
|
);
|
|
3079
4694
|
state.studio.editorModels = [originalModel, modifiedModel];
|
|
3080
4695
|
state.studio.editor = monaco.editor.createDiffEditor(container, {
|
|
3081
|
-
theme:
|
|
4696
|
+
theme: studioMonacoTheme(),
|
|
3082
4697
|
readOnly: true,
|
|
3083
4698
|
automaticLayout: true,
|
|
3084
4699
|
minimap: { enabled: false },
|
|
@@ -3101,7 +4716,7 @@ async function mountStudioEditor(content) {
|
|
|
3101
4716
|
state.studio.editor = monaco.editor.create(container, {
|
|
3102
4717
|
value: content,
|
|
3103
4718
|
language: languageForPath(state.studio.selectedFile?.path ?? ""),
|
|
3104
|
-
theme:
|
|
4719
|
+
theme: studioMonacoTheme(),
|
|
3105
4720
|
readOnly: state.studio.readOnly,
|
|
3106
4721
|
automaticLayout: true,
|
|
3107
4722
|
minimap: { enabled: false },
|
|
@@ -3112,6 +4727,11 @@ async function mountStudioEditor(content) {
|
|
|
3112
4727
|
scrollBeyondLastLine: false
|
|
3113
4728
|
});
|
|
3114
4729
|
state.studio.editorKind = "monaco";
|
|
4730
|
+
state.studio.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
4731
|
+
if (canSaveStudioFile()) {
|
|
4732
|
+
void saveStudioFile();
|
|
4733
|
+
}
|
|
4734
|
+
});
|
|
3115
4735
|
state.studio.editor.onDidChangeModelContent(() => {
|
|
3116
4736
|
state.studio.draftContent = getStudioEditorValue();
|
|
3117
4737
|
state.studio.dirty = state.studio.draftContent !== state.studio.fileContent;
|
|
@@ -3323,6 +4943,7 @@ function getStudioEditorValue() {
|
|
|
3323
4943
|
|
|
3324
4944
|
function ensureMonaco() {
|
|
3325
4945
|
if (window.monaco) {
|
|
4946
|
+
configurePressshipMonaco(window.monaco);
|
|
3326
4947
|
return Promise.resolve(window.monaco);
|
|
3327
4948
|
}
|
|
3328
4949
|
if (monacoPromise) {
|
|
@@ -3338,7 +4959,10 @@ function ensureMonaco() {
|
|
|
3338
4959
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"
|
|
3339
4960
|
}
|
|
3340
4961
|
});
|
|
3341
|
-
window.require(["vs/editor/editor.main"], () =>
|
|
4962
|
+
window.require(["vs/editor/editor.main"], () => {
|
|
4963
|
+
configurePressshipMonaco(window.monaco);
|
|
4964
|
+
resolve(window.monaco);
|
|
4965
|
+
}, reject);
|
|
3342
4966
|
};
|
|
3343
4967
|
script.onerror = () => reject(new Error("Could not load Monaco Editor."));
|
|
3344
4968
|
document.head.appendChild(script);
|
|
@@ -3347,7 +4971,132 @@ function ensureMonaco() {
|
|
|
3347
4971
|
return monacoPromise;
|
|
3348
4972
|
}
|
|
3349
4973
|
|
|
4974
|
+
function configurePressshipMonaco(monaco) {
|
|
4975
|
+
if (!monaco || monacoConfigured) {
|
|
4976
|
+
return;
|
|
4977
|
+
}
|
|
4978
|
+
monacoConfigured = true;
|
|
4979
|
+
registerWordPressReadmeLanguage(monaco);
|
|
4980
|
+
definePressshipMonacoThemes(monaco);
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
function registerWordPressReadmeLanguage(monaco) {
|
|
4984
|
+
const languageId = "wordpress-readme";
|
|
4985
|
+
if (!monaco.languages.getLanguages().some((language) => language.id === languageId)) {
|
|
4986
|
+
monaco.languages.register({
|
|
4987
|
+
id: languageId,
|
|
4988
|
+
aliases: ["WordPress Readme", "wordpress-readme"],
|
|
4989
|
+
extensions: [".txt"],
|
|
4990
|
+
filenames: ["readme.txt"]
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
monaco.languages.setLanguageConfiguration(languageId, {
|
|
4995
|
+
brackets: [
|
|
4996
|
+
["[", "]"],
|
|
4997
|
+
["(", ")"],
|
|
4998
|
+
["`", "`"]
|
|
4999
|
+
],
|
|
5000
|
+
autoClosingPairs: [
|
|
5001
|
+
{ open: "`", close: "`" },
|
|
5002
|
+
{ open: "[", close: "]" },
|
|
5003
|
+
{ open: "(", close: ")" }
|
|
5004
|
+
],
|
|
5005
|
+
surroundingPairs: [
|
|
5006
|
+
{ open: "`", close: "`" },
|
|
5007
|
+
{ open: "*", close: "*" },
|
|
5008
|
+
{ open: "[", close: "]" },
|
|
5009
|
+
{ open: "(", close: ")" }
|
|
5010
|
+
],
|
|
5011
|
+
wordPattern: /(-?\d+(?:\.\d+)*)|([^\s`~!@#$%^&*()=+[{\]}\\|;:'",.<>/?]+)/g
|
|
5012
|
+
});
|
|
5013
|
+
|
|
5014
|
+
monaco.languages.setMonarchTokensProvider(languageId, {
|
|
5015
|
+
defaultToken: "wp-readme.text",
|
|
5016
|
+
tokenizer: {
|
|
5017
|
+
root: [
|
|
5018
|
+
[/^\s*={3}\s*.*?\s*={3}\s*$/, "wp-readme.title"],
|
|
5019
|
+
[/^\s*={2}\s*.*?\s*={2}\s*$/, "wp-readme.section"],
|
|
5020
|
+
[/^\s*=\s*.*?\s*=\s*$/, "wp-readme.subsection"],
|
|
5021
|
+
[/^(\s*)([*+-])(\s+)/, ["wp-readme.whitespace", "wp-readme.listMarker", "wp-readme.whitespace"]],
|
|
5022
|
+
[/^(\s*)(\d+\.)(\s+)/, ["wp-readme.whitespace", "wp-readme.listMarker", "wp-readme.whitespace"]],
|
|
5023
|
+
[/^\s{4,}.*$/, "wp-readme.codeBlock"],
|
|
5024
|
+
[/^\t.*$/, "wp-readme.codeBlock"],
|
|
5025
|
+
[/^(\s*>)(.*)$/, ["wp-readme.quote", "wp-readme.quote"]],
|
|
5026
|
+
[/^([A-Za-z][A-Za-z0-9 /.-]*)(:)(.*)$/, ["wp-readme.field", "wp-readme.delimiter", "wp-readme.fieldValue"]],
|
|
5027
|
+
[/\[[^\]\n]+\]:\s*(?:https?:\/\/|mailto:|#)[^\s]+/, "wp-readme.link"],
|
|
5028
|
+
[/\[[^\]\n]+\]\([^)]+\)/, "wp-readme.link"],
|
|
5029
|
+
[/(?:https?:\/\/|mailto:)[^\s)]+/, "wp-readme.url"],
|
|
5030
|
+
[/\b(?:pressship|npx|wp|svn|npm|composer)\s+[^\n`]+/, "wp-readme.command"],
|
|
5031
|
+
[/`[^`\n]+`/, "wp-readme.inlineCode"],
|
|
5032
|
+
[/\*\*[^*\n][\s\S]*?\*\*/, "wp-readme.strong"],
|
|
5033
|
+
[/\*[^*\s\n][^*\n]*\*/, "wp-readme.emphasis"],
|
|
5034
|
+
[/\[(?:youtube|vimeo|wpvideo|playlist|audio|video)\b[^\]\n]*\]/i, "wp-readme.shortcode"],
|
|
5035
|
+
[/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/, "wp-readme.url"]
|
|
5036
|
+
]
|
|
5037
|
+
}
|
|
5038
|
+
});
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
function definePressshipMonacoThemes(monaco) {
|
|
5042
|
+
monaco.editor.defineTheme("pressship-studio-dark", {
|
|
5043
|
+
base: "vs-dark",
|
|
5044
|
+
inherit: true,
|
|
5045
|
+
rules: [
|
|
5046
|
+
{ token: "wp-readme.title", foreground: "f8fafc", fontStyle: "bold" },
|
|
5047
|
+
{ token: "wp-readme.section", foreground: "7dd3fc", fontStyle: "bold" },
|
|
5048
|
+
{ token: "wp-readme.subsection", foreground: "fbbf24", fontStyle: "bold" },
|
|
5049
|
+
{ token: "wp-readme.field", foreground: "f78c6c", fontStyle: "bold" },
|
|
5050
|
+
{ token: "wp-readme.fieldValue", foreground: "cbd5e1" },
|
|
5051
|
+
{ token: "wp-readme.delimiter", foreground: "94a3b8" },
|
|
5052
|
+
{ token: "wp-readme.listMarker", foreground: "a78bfa", fontStyle: "bold" },
|
|
5053
|
+
{ token: "wp-readme.inlineCode", foreground: "c4b5fd" },
|
|
5054
|
+
{ token: "wp-readme.codeBlock", foreground: "a7f3d0" },
|
|
5055
|
+
{ token: "wp-readme.link", foreground: "93c5fd", fontStyle: "underline" },
|
|
5056
|
+
{ token: "wp-readme.url", foreground: "67e8f9", fontStyle: "underline" },
|
|
5057
|
+
{ token: "wp-readme.command", foreground: "fde68a" },
|
|
5058
|
+
{ token: "wp-readme.shortcode", foreground: "f9a8d4" },
|
|
5059
|
+
{ token: "wp-readme.quote", foreground: "9ca3af", fontStyle: "italic" },
|
|
5060
|
+
{ token: "wp-readme.strong", foreground: "f8fafc", fontStyle: "bold" },
|
|
5061
|
+
{ token: "wp-readme.emphasis", foreground: "e5e7eb", fontStyle: "italic" }
|
|
5062
|
+
],
|
|
5063
|
+
colors: {
|
|
5064
|
+
"editor.background": "#1e1e1e"
|
|
5065
|
+
}
|
|
5066
|
+
});
|
|
5067
|
+
|
|
5068
|
+
monaco.editor.defineTheme("pressship-studio-light", {
|
|
5069
|
+
base: "vs",
|
|
5070
|
+
inherit: true,
|
|
5071
|
+
rules: [
|
|
5072
|
+
{ token: "wp-readme.title", foreground: "1d4ed8", fontStyle: "bold" },
|
|
5073
|
+
{ token: "wp-readme.section", foreground: "0f766e", fontStyle: "bold" },
|
|
5074
|
+
{ token: "wp-readme.subsection", foreground: "b45309", fontStyle: "bold" },
|
|
5075
|
+
{ token: "wp-readme.field", foreground: "be123c", fontStyle: "bold" },
|
|
5076
|
+
{ token: "wp-readme.fieldValue", foreground: "334155" },
|
|
5077
|
+
{ token: "wp-readme.delimiter", foreground: "64748b" },
|
|
5078
|
+
{ token: "wp-readme.listMarker", foreground: "7c3aed", fontStyle: "bold" },
|
|
5079
|
+
{ token: "wp-readme.inlineCode", foreground: "6d28d9" },
|
|
5080
|
+
{ token: "wp-readme.codeBlock", foreground: "047857" },
|
|
5081
|
+
{ token: "wp-readme.link", foreground: "0969da", fontStyle: "underline" },
|
|
5082
|
+
{ token: "wp-readme.url", foreground: "0284c7", fontStyle: "underline" },
|
|
5083
|
+
{ token: "wp-readme.command", foreground: "92400e" },
|
|
5084
|
+
{ token: "wp-readme.shortcode", foreground: "be185d" },
|
|
5085
|
+
{ token: "wp-readme.quote", foreground: "6b7280", fontStyle: "italic" },
|
|
5086
|
+
{ token: "wp-readme.strong", foreground: "111827", fontStyle: "bold" },
|
|
5087
|
+
{ token: "wp-readme.emphasis", foreground: "374151", fontStyle: "italic" }
|
|
5088
|
+
],
|
|
5089
|
+
colors: {
|
|
5090
|
+
"editor.background": "#ffffff"
|
|
5091
|
+
}
|
|
5092
|
+
});
|
|
5093
|
+
}
|
|
5094
|
+
|
|
3350
5095
|
function languageForPath(filePath) {
|
|
5096
|
+
const fileName = String(filePath ?? "").split("/").pop()?.toLowerCase();
|
|
5097
|
+
if (fileName === "readme.txt") {
|
|
5098
|
+
return "wordpress-readme";
|
|
5099
|
+
}
|
|
3351
5100
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
3352
5101
|
const map = {
|
|
3353
5102
|
css: "css",
|
|
@@ -3900,7 +5649,7 @@ function localCard(plugin, versionState) {
|
|
|
3900
5649
|
Details
|
|
3901
5650
|
</button>
|
|
3902
5651
|
<button type="button" role="menuitem" data-action="manage-release" data-id="${escapeAttr(plugin.id)}">
|
|
3903
|
-
<span class="dashicons
|
|
5652
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
3904
5653
|
Manage release
|
|
3905
5654
|
</button>
|
|
3906
5655
|
<button type="button" role="menuitem" data-action="version-state" data-id="${escapeAttr(plugin.id)}">
|
|
@@ -3936,7 +5685,7 @@ function localCard(plugin, versionState) {
|
|
|
3936
5685
|
Open in Studio
|
|
3937
5686
|
</button>
|
|
3938
5687
|
<button type="button" class="ps-plugin-card-secondary" data-action="manage-release" data-id="${escapeAttr(plugin.id)}" ${missing ? "disabled" : ""}>
|
|
3939
|
-
<span class="dashicons
|
|
5688
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
3940
5689
|
Manage release
|
|
3941
5690
|
</button>
|
|
3942
5691
|
</footer>
|
|
@@ -3949,6 +5698,12 @@ function localCard(plugin, versionState) {
|
|
|
3949
5698
|
* =================================================================== */
|
|
3950
5699
|
|
|
3951
5700
|
async function showDetails(scope, id) {
|
|
5701
|
+
if (state.activeView === "studio") {
|
|
5702
|
+
appendStudioCliCommand(studioCliCommand([
|
|
5703
|
+
"info",
|
|
5704
|
+
scope === "local" ? localPluginCliTarget(id) : quoteCliArg(id)
|
|
5705
|
+
]));
|
|
5706
|
+
}
|
|
3952
5707
|
els.detail.classList.add("is-open");
|
|
3953
5708
|
els.detail.setAttribute("aria-hidden", "false");
|
|
3954
5709
|
els.detail.innerHTML = `
|
|
@@ -4051,6 +5806,7 @@ function closeDetail() {
|
|
|
4051
5806
|
* =================================================================== */
|
|
4052
5807
|
|
|
4053
5808
|
async function createJob(body) {
|
|
5809
|
+
appendStudioCliCommand(studioCliCommandForJob(body));
|
|
4054
5810
|
const job = await api("/api/jobs", { method: "POST", body });
|
|
4055
5811
|
upsertJob(job);
|
|
4056
5812
|
subscribeJob(job.id);
|
|
@@ -4784,36 +6540,35 @@ function configureAutoRefresh() {
|
|
|
4784
6540
|
* View switching with view-transitions
|
|
4785
6541
|
* =================================================================== */
|
|
4786
6542
|
|
|
4787
|
-
function showView(view) {
|
|
4788
|
-
|
|
4789
|
-
|
|
6543
|
+
function showView(view, options = {}) {
|
|
6544
|
+
const nextView = normalizeViewId(view);
|
|
6545
|
+
if (state.activeView === nextView) {
|
|
6546
|
+
if (options.updateRoute !== false) {
|
|
6547
|
+
updateRouteFromState({ replace: options.replaceRoute });
|
|
6548
|
+
}
|
|
6549
|
+
return Promise.resolve();
|
|
4790
6550
|
}
|
|
4791
6551
|
const apply = () => {
|
|
4792
|
-
|
|
4793
|
-
document.body.dataset.activeView = view;
|
|
4794
|
-
document.querySelectorAll(".view").forEach((node) => node.classList.remove("is-active"));
|
|
4795
|
-
document.getElementById(`view-${view}`)?.classList.add("is-active");
|
|
4796
|
-
document
|
|
4797
|
-
.querySelectorAll("#adminmenu li")
|
|
4798
|
-
.forEach((node) => node.classList.remove("wp-has-current-submenu"));
|
|
4799
|
-
document
|
|
4800
|
-
.querySelector(`#adminmenu li[data-view="${view}"]`)
|
|
4801
|
-
?.classList.add("wp-has-current-submenu");
|
|
6552
|
+
applyActiveViewShell(nextView);
|
|
4802
6553
|
closeDetail();
|
|
4803
|
-
if (
|
|
6554
|
+
if (nextView === "release") {
|
|
4804
6555
|
if (!state.releaseBoard.loading && !state.releaseBoard.plugins.length) {
|
|
4805
6556
|
void loadReleaseBoard();
|
|
4806
6557
|
} else {
|
|
4807
6558
|
renderReleaseBoard();
|
|
4808
6559
|
}
|
|
4809
6560
|
}
|
|
6561
|
+
if (options.updateRoute !== false) {
|
|
6562
|
+
updateRouteFromState({ replace: options.replaceRoute });
|
|
6563
|
+
}
|
|
4810
6564
|
};
|
|
4811
6565
|
|
|
4812
6566
|
if (typeof document.startViewTransition === "function") {
|
|
4813
|
-
document.startViewTransition(apply);
|
|
4814
|
-
|
|
4815
|
-
apply();
|
|
6567
|
+
const transition = document.startViewTransition(apply);
|
|
6568
|
+
return transition.updateCallbackDone?.catch(() => {}) ?? Promise.resolve();
|
|
4816
6569
|
}
|
|
6570
|
+
apply();
|
|
6571
|
+
return Promise.resolve();
|
|
4817
6572
|
}
|
|
4818
6573
|
|
|
4819
6574
|
/* ===================================================================
|
|
@@ -4854,7 +6609,7 @@ function commandItems() {
|
|
|
4854
6609
|
id: "view:release",
|
|
4855
6610
|
title: "Go to Release Management",
|
|
4856
6611
|
subtitle: "Release status for every local plugin",
|
|
4857
|
-
icon: "
|
|
6612
|
+
icon: "ps-icon-rocket",
|
|
4858
6613
|
run: () => showView("release")
|
|
4859
6614
|
},
|
|
4860
6615
|
{
|
|
@@ -4914,7 +6669,7 @@ function commandItems() {
|
|
|
4914
6669
|
id: `release:${plugin.id}`,
|
|
4915
6670
|
title: `Manage release • ${plugin.name}`,
|
|
4916
6671
|
subtitle: plugin.slug,
|
|
4917
|
-
icon: "
|
|
6672
|
+
icon: "ps-icon-rocket",
|
|
4918
6673
|
run: () => {
|
|
4919
6674
|
void openStudio("local", plugin.id, { sidebarTab: "release" });
|
|
4920
6675
|
}
|
|
@@ -5215,6 +6970,16 @@ function renderStudioAiMarkdown(value) {
|
|
|
5215
6970
|
}
|
|
5216
6971
|
}
|
|
5217
6972
|
|
|
6973
|
+
function refreshStudioAiMarkdownIfReady() {
|
|
6974
|
+
try {
|
|
6975
|
+
if ((state.studio.aiMessages ?? []).some((message) => message.role === "assistant")) {
|
|
6976
|
+
updateStudioAiMessageList();
|
|
6977
|
+
}
|
|
6978
|
+
} catch {
|
|
6979
|
+
// The markdown parser can finish loading before Studio state exists.
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6982
|
+
|
|
5218
6983
|
function basicMarkdownToHtml(value) {
|
|
5219
6984
|
const lines = String(value ?? "").replace(/\r\n?/g, "\n").split("\n");
|
|
5220
6985
|
const blocks = [];
|
|
@@ -5460,6 +7225,22 @@ function isSafeMarkdownHref(value) {
|
|
|
5460
7225
|
}
|
|
5461
7226
|
}
|
|
5462
7227
|
|
|
7228
|
+
function formatStudioBytes(bytes) {
|
|
7229
|
+
const value = Number(bytes);
|
|
7230
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
7231
|
+
return "0 B";
|
|
7232
|
+
}
|
|
7233
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
7234
|
+
let size = value;
|
|
7235
|
+
let unitIndex = 0;
|
|
7236
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
7237
|
+
size /= 1024;
|
|
7238
|
+
unitIndex += 1;
|
|
7239
|
+
}
|
|
7240
|
+
const precision = unitIndex === 0 || size >= 10 ? 0 : 1;
|
|
7241
|
+
return `${size.toFixed(precision)} ${units[unitIndex]}`;
|
|
7242
|
+
}
|
|
7243
|
+
|
|
5463
7244
|
function escapeHtml(value) {
|
|
5464
7245
|
return String(value)
|
|
5465
7246
|
.replaceAll("&", "&")
|
|
@@ -5572,7 +7353,7 @@ function renderReleaseBoard() {
|
|
|
5572
7353
|
els.release.innerHTML = emptyState({
|
|
5573
7354
|
title: "No local plugins to release.",
|
|
5574
7355
|
message: "Add a plugin folder to your Local Library, then come back here.",
|
|
5575
|
-
icon: "
|
|
7356
|
+
icon: "ps-icon-rocket"
|
|
5576
7357
|
});
|
|
5577
7358
|
return;
|
|
5578
7359
|
}
|
|
@@ -5581,7 +7362,7 @@ function renderReleaseBoard() {
|
|
|
5581
7362
|
els.release.innerHTML = `
|
|
5582
7363
|
<div class="ps-card-toolbar" role="region" aria-label="Release board summary">
|
|
5583
7364
|
<span class="ps-card-toolbar-count">
|
|
5584
|
-
<span class="dashicons
|
|
7365
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
5585
7366
|
${escapeHtml(
|
|
5586
7367
|
`${state.releaseBoard.plugins.length} plugin${state.releaseBoard.plugins.length === 1 ? "" : "s"} tracked`
|
|
5587
7368
|
)}
|
|
@@ -5624,7 +7405,7 @@ function releaseBoardCard(entry) {
|
|
|
5624
7405
|
: ""}
|
|
5625
7406
|
<footer class="ps-release-board-card-footer">
|
|
5626
7407
|
<button type="button" class="button button-primary" data-action="manage-release" data-id="${escapeAttr(entry.id)}" ${entry.exists === false ? "disabled" : ""}>
|
|
5627
|
-
<span class="dashicons
|
|
7408
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
5628
7409
|
Manage release
|
|
5629
7410
|
</button>
|
|
5630
7411
|
<button type="button" class="ps-plugin-card-secondary" data-action="version-state" data-id="${escapeAttr(entry.id)}" ${entry.exists === false ? "disabled" : ""}>
|
|
@@ -5649,15 +7430,15 @@ function studioPluginKey() {
|
|
|
5649
7430
|
|
|
5650
7431
|
function setStudioSidebarTab(tab) {
|
|
5651
7432
|
const next = tab === "release" ? "release" : "ai";
|
|
5652
|
-
if (state.studio.sidebarTab === next) {
|
|
5653
|
-
return;
|
|
5654
|
-
}
|
|
5655
7433
|
state.studio.sidebarTab = next;
|
|
5656
7434
|
saveStudioSidebarTab(studioPluginKey(), next);
|
|
5657
7435
|
updateStudioSidebar();
|
|
5658
7436
|
if (next === "release" && state.studio.scope === "local" && !state.studio.release.tags) {
|
|
5659
7437
|
void loadStudioReleaseTags();
|
|
5660
7438
|
}
|
|
7439
|
+
if (next === "release" && state.studio.scope === "local") {
|
|
7440
|
+
void refreshStudioIgnoreState({ files: true });
|
|
7441
|
+
}
|
|
5661
7442
|
}
|
|
5662
7443
|
|
|
5663
7444
|
function updateStudioSidebar() {
|
|
@@ -5682,7 +7463,7 @@ function renderStudioReleasePane() {
|
|
|
5682
7463
|
return `
|
|
5683
7464
|
<div class="studio-release-pane">
|
|
5684
7465
|
<div class="studio-release-empty">
|
|
5685
|
-
<span class="dashicons
|
|
7466
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
5686
7467
|
<strong>Release management is local-only</strong>
|
|
5687
7468
|
<p>Open a local plugin to manage its release lifecycle.</p>
|
|
5688
7469
|
</div>
|
|
@@ -5693,7 +7474,7 @@ function renderStudioReleasePane() {
|
|
|
5693
7474
|
return `
|
|
5694
7475
|
<div class="studio-release-pane">
|
|
5695
7476
|
<div class="studio-release-empty">
|
|
5696
|
-
<span class="dashicons
|
|
7477
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
5697
7478
|
<strong>Open a plugin to start</strong>
|
|
5698
7479
|
</div>
|
|
5699
7480
|
</div>
|
|
@@ -5715,8 +7496,9 @@ function renderStudioReleasePane() {
|
|
|
5715
7496
|
</header>
|
|
5716
7497
|
<ol class="ps-release-funnel">
|
|
5717
7498
|
${renderStudioReleaseStepVersion(versionState, release)}
|
|
5718
|
-
${renderStudioReleaseStepTags(release)}
|
|
7499
|
+
${renderStudioReleaseStepTags(versionState, release)}
|
|
5719
7500
|
${renderStudioReleaseStepValidate(versionState, release)}
|
|
7501
|
+
${renderStudioReleaseStepIgnored()}
|
|
5720
7502
|
${renderStudioReleaseStepPublish(versionState, release)}
|
|
5721
7503
|
</ol>
|
|
5722
7504
|
</div>
|
|
@@ -5739,6 +7521,16 @@ function renderStudioReleaseStepShell(number, title, summary, body) {
|
|
|
5739
7521
|
`;
|
|
5740
7522
|
}
|
|
5741
7523
|
|
|
7524
|
+
function toggleStudioReleaseIgnored() {
|
|
7525
|
+
state.studio.release = {
|
|
7526
|
+
...state.studio.release,
|
|
7527
|
+
ignoredCollapsed: !(state.studio.release?.ignoredCollapsed ?? true)
|
|
7528
|
+
};
|
|
7529
|
+
renderStudio();
|
|
7530
|
+
remountStudioEditorIfNeeded();
|
|
7531
|
+
updateStudioControls();
|
|
7532
|
+
}
|
|
7533
|
+
|
|
5742
7534
|
function renderStudioReleaseStepVersion(versionState, release) {
|
|
5743
7535
|
const localVersion = versionState?.localVersion ?? "—";
|
|
5744
7536
|
const stable = versionState?.readmeStableTag ?? "—";
|
|
@@ -5793,7 +7585,11 @@ function renderStudioReleaseStepVersion(versionState, release) {
|
|
|
5793
7585
|
return renderStudioReleaseStepShell(1, "Version state", summary, body);
|
|
5794
7586
|
}
|
|
5795
7587
|
|
|
5796
|
-
function
|
|
7588
|
+
function releaseTagDraftValue(versionState, release) {
|
|
7589
|
+
return release.newTagDraft || versionState?.localVersion || "";
|
|
7590
|
+
}
|
|
7591
|
+
|
|
7592
|
+
function renderStudioReleaseStepTags(versionState, release) {
|
|
5797
7593
|
let body;
|
|
5798
7594
|
if (release.tagsLoading && !release.tags) {
|
|
5799
7595
|
body = loadingShell("Reading SVN tags…");
|
|
@@ -5814,22 +7610,31 @@ function renderStudioReleaseStepTags(release) {
|
|
|
5814
7610
|
const tagRows = (list.tags ?? [])
|
|
5815
7611
|
.map((tag) => renderStudioReleaseTagRow(tag))
|
|
5816
7612
|
.join("");
|
|
7613
|
+
const newTagValue = releaseTagDraftValue(versionState, release);
|
|
7614
|
+
const currentVersionTag = versionState?.localVersion
|
|
7615
|
+
? (list.tags ?? []).find((tag) => tag.name === versionState.localVersion)
|
|
7616
|
+
: null;
|
|
7617
|
+
const newTagControl = currentVersionTag
|
|
7618
|
+
? currentVersionTag.isUncommitted
|
|
7619
|
+
? `<p class="ps-release-tag-ready"><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>${escapeHtml(`Tag ${versionState.localVersion} is ready. Run a dry-run release next.`)}</p>`
|
|
7620
|
+
: `<p class="ps-release-tag-ready is-blocked"><span class="dashicons dashicons-warning" aria-hidden="true"></span>${escapeHtml(`Tag ${versionState.localVersion} already exists on WordPress.org SVN. Bump the version before creating a new tag.`)}</p>`
|
|
7621
|
+
: `<div class="ps-release-step-newtag">
|
|
7622
|
+
<label>
|
|
7623
|
+
<span>Create tag from current version</span>
|
|
7624
|
+
<input type="text" id="studio-release-new-tag" value="${escapeAttr(newTagValue)}" placeholder="1.2.3" />
|
|
7625
|
+
</label>
|
|
7626
|
+
<button class="button button-secondary" type="button" data-action="studio-release-create">
|
|
7627
|
+
<span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>
|
|
7628
|
+
Create tag
|
|
7629
|
+
</button>
|
|
7630
|
+
</div>`;
|
|
5817
7631
|
body = `
|
|
5818
7632
|
<ul class="ps-release-tag-list">
|
|
5819
7633
|
${trunkRow}
|
|
5820
7634
|
${tagRows || `<li class="ps-release-tag-empty">No tags yet.</li>`}
|
|
5821
7635
|
</ul>
|
|
5822
7636
|
${renderStudioReleaseSwitchConflict(release)}
|
|
5823
|
-
|
|
5824
|
-
<label>
|
|
5825
|
-
<span>Create tag from current version</span>
|
|
5826
|
-
<input type="text" id="studio-release-new-tag" value="${escapeAttr(release.newTagDraft ?? "")}" placeholder="1.2.3" />
|
|
5827
|
-
</label>
|
|
5828
|
-
<button class="button button-secondary" type="button" data-action="studio-release-create">
|
|
5829
|
-
<span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>
|
|
5830
|
-
Create tag
|
|
5831
|
-
</button>
|
|
5832
|
-
</div>
|
|
7637
|
+
${newTagControl}
|
|
5833
7638
|
${release.newTagError ? `<p class="ps-release-inline-error"><span class="dashicons dashicons-warning" aria-hidden="true"></span>${escapeHtml(release.newTagError)}</p>` : ""}
|
|
5834
7639
|
`;
|
|
5835
7640
|
}
|
|
@@ -5881,9 +7686,11 @@ function renderStudioReleaseTagRow(tag) {
|
|
|
5881
7686
|
const switchingLabel = release.switchingResolution === "override" || release.switchingResolution === "revert"
|
|
5882
7687
|
? "Resolving"
|
|
5883
7688
|
: "Switching";
|
|
5884
|
-
const switchButton = tag.
|
|
5885
|
-
? ""
|
|
5886
|
-
:
|
|
7689
|
+
const switchButton = tag.isUncommitted
|
|
7690
|
+
? `<span class="ps-release-tag-local-note" title="This tag exists locally and will be published by the release step.">Ready for release</span>`
|
|
7691
|
+
: tag.isCurrent
|
|
7692
|
+
? ""
|
|
7693
|
+
: `<button class="button button-small" type="button" data-action="studio-release-switch" data-tag="${escapeAttr(tag.name)}" ${anySwitching ? "disabled aria-disabled=\"true\"" : ""}>${switching ? `<span class="dashicons dashicons-update" aria-hidden="true"></span>${switchingLabel}` : "Switch"}</button>`;
|
|
5887
7694
|
const confirmKey = `delete-tag:${tag.name}`;
|
|
5888
7695
|
const pendingConfirm = state.studio.pendingConfirms?.get(confirmKey);
|
|
5889
7696
|
const deleteButton = tag.isUncommitted
|
|
@@ -5939,6 +7746,183 @@ function renderStudioReleaseStepValidate(versionState, release) {
|
|
|
5939
7746
|
return renderStudioReleaseStepShell(3, "Validate", summaryLine, body);
|
|
5940
7747
|
}
|
|
5941
7748
|
|
|
7749
|
+
function renderStudioReleaseStepIgnored() {
|
|
7750
|
+
const ignoreState = state.studio.ignoreState ?? createInitialStudioIgnoreState();
|
|
7751
|
+
const patterns = ignoreState.patterns ?? [];
|
|
7752
|
+
const ignoredFiles = ignoreState.ignoredFiles ?? [];
|
|
7753
|
+
const collapsed = state.studio.release?.ignoredCollapsed ?? true;
|
|
7754
|
+
const summary = patterns.length
|
|
7755
|
+
? `${patterns.length} pattern${patterns.length === 1 ? "" : "s"} · ${ignoredFiles.length} file${ignoredFiles.length === 1 ? "" : "s"}`
|
|
7756
|
+
: "No project ignore rules yet.";
|
|
7757
|
+
|
|
7758
|
+
let body;
|
|
7759
|
+
if (state.studio.ignoreLoading) {
|
|
7760
|
+
body = loadingShell("Reading ignored files…");
|
|
7761
|
+
} else if (state.studio.ignoreError) {
|
|
7762
|
+
body = `<p class="ps-release-step-error">${escapeHtml(state.studio.ignoreError)}</p>`;
|
|
7763
|
+
} else if (!patterns.length) {
|
|
7764
|
+
body = `<p class="ps-release-step-muted">No project ignore rules yet.</p>`;
|
|
7765
|
+
} else {
|
|
7766
|
+
body = `
|
|
7767
|
+
<ul class="ps-release-ignore-patterns">
|
|
7768
|
+
${patterns.map((pattern) => renderStudioIgnorePatternRow(pattern)).join("")}
|
|
7769
|
+
</ul>
|
|
7770
|
+
${ignoredFiles.length
|
|
7771
|
+
? `<ul class="ps-release-ignored-files">
|
|
7772
|
+
${ignoredFiles.slice(0, 8).map((file) => `
|
|
7773
|
+
<li>
|
|
7774
|
+
<span class="dashicons ${studioFileIcon(file.path)}" aria-hidden="true"></span>
|
|
7775
|
+
<span title="${escapeAttr(file.path)}">${escapeHtml(file.path)}</span>
|
|
7776
|
+
<small>${escapeHtml(file.ignoredBy ?? "")}</small>
|
|
7777
|
+
</li>
|
|
7778
|
+
`).join("")}
|
|
7779
|
+
</ul>
|
|
7780
|
+
${ignoredFiles.length > 8 ? `<p class="ps-release-step-muted">${escapeHtml(`${ignoredFiles.length - 8} more ignored file${ignoredFiles.length - 8 === 1 ? "" : "s"}.`)}</p>` : ""}`
|
|
7781
|
+
: `<p class="ps-release-step-muted">The patterns do not match any visible project files.</p>`}
|
|
7782
|
+
`;
|
|
7783
|
+
}
|
|
7784
|
+
|
|
7785
|
+
return `
|
|
7786
|
+
<li class="ps-release-step ps-release-step-collapsible${collapsed ? " is-collapsed" : ""}">
|
|
7787
|
+
<span class="ps-release-step-connector" aria-hidden="true"></span>
|
|
7788
|
+
<header class="ps-release-step-header">
|
|
7789
|
+
<button class="ps-release-step-toggle" type="button" data-action="studio-release-toggle-ignored" aria-expanded="${collapsed ? "false" : "true"}">
|
|
7790
|
+
<span class="ps-release-step-marker">4</span>
|
|
7791
|
+
<span class="ps-release-step-heading">
|
|
7792
|
+
<strong>Ignored files</strong>
|
|
7793
|
+
<small>${escapeHtml(summary)}</small>
|
|
7794
|
+
</span>
|
|
7795
|
+
<span class="dashicons ${collapsed ? "dashicons-arrow-right-alt2" : "dashicons-arrow-down-alt2"} ps-release-step-toggle-icon" aria-hidden="true"></span>
|
|
7796
|
+
</button>
|
|
7797
|
+
</header>
|
|
7798
|
+
${collapsed ? "" : `<div class="ps-release-step-body">${body}</div>`}
|
|
7799
|
+
</li>
|
|
7800
|
+
`;
|
|
7801
|
+
}
|
|
7802
|
+
|
|
7803
|
+
function renderStudioIgnorePatternRow(pattern) {
|
|
7804
|
+
const busy = state.studio.ignoreBusyPattern === pattern;
|
|
7805
|
+
return `
|
|
7806
|
+
<li>
|
|
7807
|
+
<code>${escapeHtml(pattern)}</code>
|
|
7808
|
+
<button class="button button-small ps-release-ignore-remove" type="button" data-action="studio-unignore-rule" data-pattern="${escapeAttr(pattern)}" title="${escapeAttr(`Remove ${pattern}`)}" aria-label="${escapeAttr(`Remove ${pattern}`)}" ${busy ? "disabled aria-busy=\"true\"" : ""}>
|
|
7809
|
+
<span class="dashicons ${busy ? "dashicons-update" : "dashicons-no-alt"}" aria-hidden="true"></span>
|
|
7810
|
+
</button>
|
|
7811
|
+
</li>
|
|
7812
|
+
`;
|
|
7813
|
+
}
|
|
7814
|
+
|
|
7815
|
+
function studioPublishRouteDetails(action) {
|
|
7816
|
+
switch (action) {
|
|
7817
|
+
case "submit":
|
|
7818
|
+
return {
|
|
7819
|
+
label: "Submit new plugin",
|
|
7820
|
+
shortLabel: "submit",
|
|
7821
|
+
icon: "dashicons-upload",
|
|
7822
|
+
description: "Use this for a first WordPress.org review. Pressship builds a package and uploads it to the plugin submission flow after confirmation.",
|
|
7823
|
+
dryRunLabel: "Dry-run submit",
|
|
7824
|
+
confirmLabel: "Confirm submit",
|
|
7825
|
+
resultLabel: "WordPress.org submission"
|
|
7826
|
+
};
|
|
7827
|
+
case "release":
|
|
7828
|
+
return {
|
|
7829
|
+
label: "Release update",
|
|
7830
|
+
shortLabel: "release",
|
|
7831
|
+
icon: "ps-icon-rocket",
|
|
7832
|
+
description: "Use this for an approved plugin that already has an SVN repository. Pressship validates the package and publishes the current version after confirmation.",
|
|
7833
|
+
dryRunLabel: "Dry-run release",
|
|
7834
|
+
confirmLabel: "Confirm release",
|
|
7835
|
+
resultLabel: "SVN release"
|
|
7836
|
+
};
|
|
7837
|
+
default:
|
|
7838
|
+
return {
|
|
7839
|
+
label: "Auto decide",
|
|
7840
|
+
shortLabel: "auto",
|
|
7841
|
+
icon: "dashicons-controls-play",
|
|
7842
|
+
description: "Best default. Pressship checks WordPress.org and SVN, then chooses submit for first-time review or release for an existing plugin.",
|
|
7843
|
+
dryRunLabel: "Dry-run auto",
|
|
7844
|
+
confirmLabel: "Confirm publish",
|
|
7845
|
+
resultLabel: "publish route"
|
|
7846
|
+
};
|
|
7847
|
+
}
|
|
7848
|
+
}
|
|
7849
|
+
|
|
7850
|
+
function renderStudioReleasePublishOption(action, options = {}) {
|
|
7851
|
+
const details = studioPublishRouteDetails(action);
|
|
7852
|
+
const active = options.activeAction === action;
|
|
7853
|
+
const chosen = options.detectedRoute === action;
|
|
7854
|
+
const isDefault = options.defaultAction === action;
|
|
7855
|
+
const recommended = action === "auto";
|
|
7856
|
+
const disabled = options.running ? "disabled aria-disabled=\"true\"" : "";
|
|
7857
|
+
const badges = [
|
|
7858
|
+
recommended ? "Recommended" : "",
|
|
7859
|
+
isDefault ? "Default" : "",
|
|
7860
|
+
chosen ? "Selected" : ""
|
|
7861
|
+
].filter(Boolean);
|
|
7862
|
+
|
|
7863
|
+
return `
|
|
7864
|
+
<button class="ps-release-publish-option${active ? " is-active" : ""}${chosen ? " is-selected" : ""}" type="button" data-action="dry-run-publish" data-id="${escapeAttr(state.studio.id)}" data-publish-action="${escapeAttr(action)}" ${disabled}>
|
|
7865
|
+
<span class="ps-release-option-icon dashicons ${escapeAttr(details.icon)}" aria-hidden="true"></span>
|
|
7866
|
+
<span class="ps-release-option-content">
|
|
7867
|
+
<span class="ps-release-option-title">
|
|
7868
|
+
<strong>${escapeHtml(details.label)}</strong>
|
|
7869
|
+
${badges.map((badge) => `<em>${escapeHtml(badge)}</em>`).join("")}
|
|
7870
|
+
</span>
|
|
7871
|
+
<span class="ps-release-option-copy">${escapeHtml(details.description)}</span>
|
|
7872
|
+
</span>
|
|
7873
|
+
<span class="ps-release-option-action">${escapeHtml(details.dryRunLabel)}</span>
|
|
7874
|
+
</button>
|
|
7875
|
+
`;
|
|
7876
|
+
}
|
|
7877
|
+
|
|
7878
|
+
function renderStudioReleasePublishSummary(dryRun, pendingConfirm, running) {
|
|
7879
|
+
if (running) {
|
|
7880
|
+
return `
|
|
7881
|
+
<div class="ps-release-publish-summary is-running" role="status">
|
|
7882
|
+
<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
7883
|
+
<p>
|
|
7884
|
+
<strong>Previewing publish route</strong>
|
|
7885
|
+
<small>Validating the package and checking which path is safe to confirm.</small>
|
|
7886
|
+
</p>
|
|
7887
|
+
</div>
|
|
7888
|
+
`;
|
|
7889
|
+
}
|
|
7890
|
+
|
|
7891
|
+
if (!dryRun) {
|
|
7892
|
+
return `
|
|
7893
|
+
<p class="ps-release-step-muted">
|
|
7894
|
+
Pick a dry-run path above. A dry-run validates the package and shows exactly what will happen; nothing is uploaded or committed until you confirm.
|
|
7895
|
+
</p>
|
|
7896
|
+
`;
|
|
7897
|
+
}
|
|
7898
|
+
|
|
7899
|
+
const routeAction = dryRun.route?.action ?? "publish";
|
|
7900
|
+
const details = studioPublishRouteDetails(routeAction);
|
|
7901
|
+
const packageSummary = dryRun.package?.fileCount
|
|
7902
|
+
? `${dryRun.package.fileCount} packaged files`
|
|
7903
|
+
: dryRun.package?.topLevelFolder
|
|
7904
|
+
? `Package folder: ${dryRun.package.topLevelFolder}`
|
|
7905
|
+
: "";
|
|
7906
|
+
|
|
7907
|
+
return `
|
|
7908
|
+
<div class="ps-release-publish-summary">
|
|
7909
|
+
<div class="ps-release-publish-result">
|
|
7910
|
+
<span class="dashicons ${escapeAttr(details.icon)}" aria-hidden="true"></span>
|
|
7911
|
+
<p>
|
|
7912
|
+
<strong>${escapeHtml(details.label)}</strong>
|
|
7913
|
+
<small>${escapeHtml(dryRun.route?.reason ?? `Ready to preview ${details.resultLabel}.`)}</small>
|
|
7914
|
+
${packageSummary ? `<small>${escapeHtml(packageSummary)}</small>` : ""}
|
|
7915
|
+
</p>
|
|
7916
|
+
</div>
|
|
7917
|
+
${dryRun.canConfirm && dryRun.approvalId
|
|
7918
|
+
? `<button class="button button-primary ps-release-confirm-button${pendingConfirm ? " is-confirming" : ""}" type="button" data-action="studio-release-publish" data-approval-id="${escapeAttr(dryRun.approvalId)}" data-action-label="${escapeAttr(routeAction)}">
|
|
7919
|
+
${pendingConfirm ? "Click again to confirm" : escapeHtml(details.confirmLabel)}
|
|
7920
|
+
</button>`
|
|
7921
|
+
: `<p class="ps-release-step-muted">Dry-run did not pass. Fix validation or version findings before publishing.</p>`}
|
|
7922
|
+
</div>
|
|
7923
|
+
`;
|
|
7924
|
+
}
|
|
7925
|
+
|
|
5942
7926
|
function renderStudioReleaseStepPublish(versionState, release) {
|
|
5943
7927
|
const defaultAction = state.settings?.defaultPublishAction ?? "auto";
|
|
5944
7928
|
const dryRun = release.dryRun;
|
|
@@ -5946,44 +7930,28 @@ function renderStudioReleaseStepPublish(versionState, release) {
|
|
|
5946
7930
|
const publishConfirmKey = "publish";
|
|
5947
7931
|
const pendingConfirm = state.studio.pendingConfirms?.get(publishConfirmKey);
|
|
5948
7932
|
const detectedRoute = dryRun?.route?.action;
|
|
7933
|
+
const activeAction = detectedRoute ?? defaultAction;
|
|
5949
7934
|
|
|
5950
7935
|
const body = `
|
|
7936
|
+
<div class="ps-release-publish-guide">
|
|
7937
|
+
<strong>Dry-run first, confirm second.</strong>
|
|
7938
|
+
<span>Choose the route you want to preview. The dry-run is safe; the later confirm button performs the upload or SVN publish.</span>
|
|
7939
|
+
</div>
|
|
5951
7940
|
<div class="ps-release-publish-options">
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
</button>
|
|
5956
|
-
<button class="button${detectedRoute === "release" ? " button-primary" : ""}" type="button" data-action="dry-run-publish" data-id="${escapeAttr(state.studio.id)}" data-publish-action="release" ${running ? "disabled" : ""}>
|
|
5957
|
-
<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
5958
|
-
Dry-run release
|
|
5959
|
-
</button>
|
|
5960
|
-
<button class="button${defaultAction === "auto" ? " button-primary" : ""}" type="button" data-action="dry-run-publish" data-id="${escapeAttr(state.studio.id)}" data-publish-action="auto" ${running ? "disabled" : ""}>
|
|
5961
|
-
<span class="dashicons dashicons-controls-play" aria-hidden="true"></span>
|
|
5962
|
-
Auto dry-run
|
|
5963
|
-
</button>
|
|
7941
|
+
${renderStudioReleasePublishOption("auto", { activeAction, defaultAction, detectedRoute, running })}
|
|
7942
|
+
${renderStudioReleasePublishOption("submit", { activeAction, defaultAction, detectedRoute, running })}
|
|
7943
|
+
${renderStudioReleasePublishOption("release", { activeAction, defaultAction, detectedRoute, running })}
|
|
5964
7944
|
</div>
|
|
5965
|
-
${dryRun
|
|
5966
|
-
? `<div class="ps-release-publish-summary">
|
|
5967
|
-
<p>
|
|
5968
|
-
<strong>${escapeHtml(dryRun.route?.action ?? "publish")}</strong>
|
|
5969
|
-
<small>${escapeHtml(dryRun.route?.reason ?? "")}</small>
|
|
5970
|
-
</p>
|
|
5971
|
-
${dryRun.canConfirm && dryRun.approvalId
|
|
5972
|
-
? `<button class="button button-primary ps-release-confirm-button${pendingConfirm ? " is-confirming" : ""}" type="button" data-action="studio-release-publish" data-approval-id="${escapeAttr(dryRun.approvalId)}" data-action-label="${escapeAttr(dryRun.route?.action ?? "publish")}">
|
|
5973
|
-
${pendingConfirm ? "Click again to confirm" : `Confirm ${escapeHtml(dryRun.route?.action ?? "publish")}`}
|
|
5974
|
-
</button>`
|
|
5975
|
-
: `<p class="ps-release-step-muted">Dry-run did not pass — fix validation findings before publishing.</p>`}
|
|
5976
|
-
</div>`
|
|
5977
|
-
: `<p class="ps-release-step-muted">Run a dry-run to preview the publish plan.</p>`}
|
|
7945
|
+
${renderStudioReleasePublishSummary(dryRun, pendingConfirm, running)}
|
|
5978
7946
|
`;
|
|
5979
7947
|
|
|
5980
7948
|
const summary = dryRun
|
|
5981
|
-
? `Detected route: ${escapeHtml(dryRun.route?.action
|
|
7949
|
+
? `Detected route: ${escapeHtml(studioPublishRouteDetails(dryRun.route?.action).shortLabel)}`
|
|
5982
7950
|
: versionState?.releaseBlocked
|
|
5983
7951
|
? "Blocked — fix version state before publishing"
|
|
5984
7952
|
: "";
|
|
5985
7953
|
|
|
5986
|
-
return renderStudioReleaseStepShell(
|
|
7954
|
+
return renderStudioReleaseStepShell(5, "Submit / Release", summary, body);
|
|
5987
7955
|
}
|
|
5988
7956
|
|
|
5989
7957
|
/* ===================================================================
|
|
@@ -6020,16 +7988,19 @@ async function refreshStudioAfterReleaseSwitch(result = {}) {
|
|
|
6020
7988
|
const localId = state.studio.id;
|
|
6021
7989
|
const selectedPath = state.studio.selectedFile?.path;
|
|
6022
7990
|
try {
|
|
6023
|
-
const [detail, filesResult, checkState, versionState] = await Promise.all([
|
|
7991
|
+
const [detail, filesResult, checkState, versionState, ignoreState] = await Promise.all([
|
|
6024
7992
|
api(`/api/plugins/local/${encodeURIComponent(localId)}`),
|
|
6025
7993
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/files`),
|
|
6026
7994
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/check-state`).catch(() => ({ state: null })),
|
|
6027
|
-
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null)
|
|
7995
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null),
|
|
7996
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/ignore-state`).catch(() => createInitialStudioIgnoreState())
|
|
6028
7997
|
]);
|
|
6029
7998
|
|
|
6030
7999
|
applyStudioPluginDetail("local", localId, detail);
|
|
6031
8000
|
state.studio.files = filesResult.files ?? [];
|
|
8001
|
+
state.studio.directories = filesResult.directories ?? [];
|
|
6032
8002
|
applyStudioCheckState(checkState.state);
|
|
8003
|
+
applyStudioIgnoreState(ignoreState);
|
|
6033
8004
|
if (versionState) {
|
|
6034
8005
|
state.versionStates.set(localId, versionState);
|
|
6035
8006
|
renderLocal();
|
|
@@ -6060,10 +8031,54 @@ async function refreshStudioAfterReleaseSwitch(result = {}) {
|
|
|
6060
8031
|
}
|
|
6061
8032
|
}
|
|
6062
8033
|
|
|
8034
|
+
async function refreshStudioAfterVersionChange(localId) {
|
|
8035
|
+
if (!localId || state.studio.id !== localId || state.studio.scope !== "local") {
|
|
8036
|
+
return;
|
|
8037
|
+
}
|
|
8038
|
+
|
|
8039
|
+
const selectedPath = state.studio.selectedFile?.path;
|
|
8040
|
+
try {
|
|
8041
|
+
const [detail, filesResult, checkState, versionState, ignoreState] = await Promise.all([
|
|
8042
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}`),
|
|
8043
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/files`),
|
|
8044
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/check-state`).catch(() => ({ state: null })),
|
|
8045
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null),
|
|
8046
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/ignore-state`).catch(() => createInitialStudioIgnoreState())
|
|
8047
|
+
]);
|
|
8048
|
+
|
|
8049
|
+
applyStudioPluginDetail("local", localId, detail);
|
|
8050
|
+
state.studio.files = filesResult.files ?? [];
|
|
8051
|
+
state.studio.directories = filesResult.directories ?? [];
|
|
8052
|
+
applyStudioCheckState(checkState.state);
|
|
8053
|
+
applyStudioIgnoreState(ignoreState);
|
|
8054
|
+
if (versionState) {
|
|
8055
|
+
state.versionStates.set(localId, versionState);
|
|
8056
|
+
if (!state.studio.release.newTagDraft || state.studio.release.newTagDraft === versionState.latestSvnTag) {
|
|
8057
|
+
state.studio.release.newTagDraft = versionState.localVersion ?? "";
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
|
|
8061
|
+
const selectedStillExists = selectedPath && state.studio.files.some((file) => file.path === selectedPath);
|
|
8062
|
+
if (selectedStillExists) {
|
|
8063
|
+
await selectStudioFile(selectedPath, { force: true });
|
|
8064
|
+
} else {
|
|
8065
|
+
renderStudio();
|
|
8066
|
+
remountStudioEditorIfNeeded();
|
|
8067
|
+
}
|
|
8068
|
+
updateStudioSidebar();
|
|
8069
|
+
updateStudioControls();
|
|
8070
|
+
} catch (error) {
|
|
8071
|
+
appendStudioTerminal(`Studio reload after version change failed: ${error.message}`, "error");
|
|
8072
|
+
renderStudio();
|
|
8073
|
+
updateStudioSidebar();
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
|
|
6063
8077
|
async function createStudioReleaseTag() {
|
|
6064
8078
|
if (!state.studio.id) return;
|
|
6065
8079
|
const input = document.getElementById("studio-release-new-tag");
|
|
6066
|
-
const
|
|
8080
|
+
const versionState = state.versionStates.get(state.studio.id);
|
|
8081
|
+
const name = (input?.value ?? releaseTagDraftValue(versionState, state.studio.release)).trim();
|
|
6067
8082
|
if (!name) {
|
|
6068
8083
|
state.studio.release.newTagError = "Enter a tag name first.";
|
|
6069
8084
|
updateStudioSidebar();
|
|
@@ -6134,6 +8149,7 @@ async function switchStudioReleaseTag(tag, resolution) {
|
|
|
6134
8149
|
|
|
6135
8150
|
async function bumpStudioReleaseVersion(localId, bump) {
|
|
6136
8151
|
if (!localId || !["patch", "minor", "major"].includes(bump)) return;
|
|
8152
|
+
appendStudioCliCommand(studioCliCommand(["version", bump, localPluginCliTarget(localId)]));
|
|
6137
8153
|
if (state.studio.id !== localId) {
|
|
6138
8154
|
// Outside the funnel (e.g. invoked from a command palette). Run without
|
|
6139
8155
|
// the inline busy state — the global notice is enough feedback.
|
|
@@ -6174,7 +8190,7 @@ async function bumpStudioReleaseVersion(localId, bump) {
|
|
|
6174
8190
|
}, 1500);
|
|
6175
8191
|
await loadLocal();
|
|
6176
8192
|
if (state.studio.id === localId) {
|
|
6177
|
-
await
|
|
8193
|
+
await refreshStudioAfterVersionChange(localId);
|
|
6178
8194
|
}
|
|
6179
8195
|
} catch (error) {
|
|
6180
8196
|
state.studio.release.bumpInFlight = null;
|
|
@@ -6218,7 +8234,7 @@ async function setStudioCustomReleaseVersion(localId) {
|
|
|
6218
8234
|
}, 1500);
|
|
6219
8235
|
await loadLocal();
|
|
6220
8236
|
if (state.studio.id === localId) {
|
|
6221
|
-
await
|
|
8237
|
+
await refreshStudioAfterVersionChange(localId);
|
|
6222
8238
|
}
|
|
6223
8239
|
} catch (error) {
|
|
6224
8240
|
state.studio.release.bumpInFlight = null;
|
|
@@ -6231,6 +8247,9 @@ function applyStudioVersionChangeResult(localId, result) {
|
|
|
6231
8247
|
if (result && typeof result === "object") {
|
|
6232
8248
|
const { checkState, ...versionState } = result;
|
|
6233
8249
|
state.versionStates.set(localId, versionState);
|
|
8250
|
+
if (state.studio.id === localId && versionState.localVersion) {
|
|
8251
|
+
state.studio.release.newTagDraft = versionState.localVersion;
|
|
8252
|
+
}
|
|
6234
8253
|
}
|
|
6235
8254
|
if (state.studio.id === localId && result && Object.prototype.hasOwnProperty.call(result, "checkState")) {
|
|
6236
8255
|
applyStudioCheckState(result.checkState);
|
|
@@ -6246,6 +8265,9 @@ async function refreshStudioVersionState() {
|
|
|
6246
8265
|
`/api/plugins/local/${encodeURIComponent(state.studio.id)}/version-state`
|
|
6247
8266
|
);
|
|
6248
8267
|
state.versionStates.set(state.studio.id, versionState);
|
|
8268
|
+
if (!state.studio.release.newTagDraft && versionState.localVersion) {
|
|
8269
|
+
state.studio.release.newTagDraft = versionState.localVersion;
|
|
8270
|
+
}
|
|
6249
8271
|
updateStudioSidebar();
|
|
6250
8272
|
} catch (error) {
|
|
6251
8273
|
// ignore — sidebar will keep stale data and notice was already shown
|