pressship 0.1.14 → 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 +1198 -73
- package/assets/web/style.css +552 -41
- 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/server.js +427 -26
- package/dist/web/server.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 }) => {
|
|
@@ -202,7 +206,7 @@ function studioTheme() {
|
|
|
202
206
|
}
|
|
203
207
|
|
|
204
208
|
function studioMonacoTheme() {
|
|
205
|
-
return studioTheme() === "light" ? "
|
|
209
|
+
return studioTheme() === "light" ? "pressship-studio-light" : "pressship-studio-dark";
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
function createInitialStudioRelease() {
|
|
@@ -227,7 +231,31 @@ function createInitialStudioRelease() {
|
|
|
227
231
|
switchingResolution: "",
|
|
228
232
|
switchJobId: null,
|
|
229
233
|
switchConflict: null,
|
|
230
|
-
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
|
|
231
259
|
};
|
|
232
260
|
}
|
|
233
261
|
|
|
@@ -400,6 +428,7 @@ const state = {
|
|
|
400
428
|
id: null,
|
|
401
429
|
plugin: null,
|
|
402
430
|
files: [],
|
|
431
|
+
directories: [],
|
|
403
432
|
selectedFile: null,
|
|
404
433
|
fileContent: "",
|
|
405
434
|
draftContent: "",
|
|
@@ -420,6 +449,8 @@ const state = {
|
|
|
420
449
|
openFiles: [],
|
|
421
450
|
terminalOpen: true,
|
|
422
451
|
collapsedFolders: new Set(),
|
|
452
|
+
expandedIgnoredFolders: new Set(),
|
|
453
|
+
loadingFolders: new Set(),
|
|
423
454
|
terminal: [],
|
|
424
455
|
aiPrompt: "",
|
|
425
456
|
aiJobId: null,
|
|
@@ -438,6 +469,12 @@ const state = {
|
|
|
438
469
|
sidebarTab: "ai",
|
|
439
470
|
playgroundVersionModal: null,
|
|
440
471
|
release: createInitialStudioRelease(),
|
|
472
|
+
ignoreState: createInitialStudioIgnoreState(),
|
|
473
|
+
packageSize: createInitialStudioPackageSize(),
|
|
474
|
+
ignoreLoading: false,
|
|
475
|
+
ignoreError: "",
|
|
476
|
+
ignoreBusyPattern: "",
|
|
477
|
+
ignoreBusyPath: "",
|
|
441
478
|
pendingConfirms: new Map()
|
|
442
479
|
},
|
|
443
480
|
activeView: "dashboard",
|
|
@@ -504,6 +541,9 @@ const ROUTE_VIEW_ALIASES = {
|
|
|
504
541
|
};
|
|
505
542
|
let applyingLocationRoute = false;
|
|
506
543
|
let initialRouteLoaderVisible = false;
|
|
544
|
+
let studioFileSyncInFlight = false;
|
|
545
|
+
let studioLastExternalSyncAt = 0;
|
|
546
|
+
let studioLastDiskConflictKey = "";
|
|
507
547
|
|
|
508
548
|
document.querySelectorAll("[data-kbd-mod]").forEach((node) => {
|
|
509
549
|
node.textContent = isMac ? "⌘" : "Ctrl+";
|
|
@@ -564,6 +604,14 @@ document.addEventListener("keydown", (event) => {
|
|
|
564
604
|
return;
|
|
565
605
|
}
|
|
566
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
|
+
|
|
567
615
|
if (event.key === "Escape" && state.command.open) {
|
|
568
616
|
event.preventDefault();
|
|
569
617
|
closeCommandPalette();
|
|
@@ -611,6 +659,16 @@ window.addEventListener("popstate", () => {
|
|
|
611
659
|
void applyLocationRoute({ replaceRoute: true });
|
|
612
660
|
});
|
|
613
661
|
|
|
662
|
+
window.addEventListener("focus", () => {
|
|
663
|
+
syncSelectedStudioFileOnResume();
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
document.addEventListener("visibilitychange", () => {
|
|
667
|
+
if (!document.hidden) {
|
|
668
|
+
syncSelectedStudioFileOnResume();
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
|
|
614
672
|
function normalizeViewId(view) {
|
|
615
673
|
return Object.prototype.hasOwnProperty.call(VIEW_ROUTE_PATHS, view) ? view : "dashboard";
|
|
616
674
|
}
|
|
@@ -722,6 +780,7 @@ function primeInitialRouteState(route) {
|
|
|
722
780
|
id: route.studio.project,
|
|
723
781
|
plugin: { slug: route.studio.project, name: route.studio.project },
|
|
724
782
|
files: [],
|
|
783
|
+
directories: [],
|
|
725
784
|
selectedFile: filePath
|
|
726
785
|
? {
|
|
727
786
|
path: filePath,
|
|
@@ -741,8 +800,13 @@ function primeInitialRouteState(route) {
|
|
|
741
800
|
checkJobId: null,
|
|
742
801
|
activeTab: "editor",
|
|
743
802
|
openFiles: filePath ? [filePath] : [],
|
|
744
|
-
terminal: [
|
|
803
|
+
terminal: [
|
|
804
|
+
`Opening ${route.studio.project} from URL...`,
|
|
805
|
+
{ message: `$ ${studioOpenCliCommand()}`, tone: "command" }
|
|
806
|
+
],
|
|
745
807
|
collapsedFolders: new Set(),
|
|
808
|
+
expandedIgnoredFolders: new Set(),
|
|
809
|
+
loadingFolders: new Set(),
|
|
746
810
|
pendingConfirms: new Map()
|
|
747
811
|
};
|
|
748
812
|
}
|
|
@@ -926,12 +990,30 @@ async function runAction(name, element) {
|
|
|
926
990
|
await selectStudioFile(element.dataset.path);
|
|
927
991
|
return;
|
|
928
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
|
+
|
|
929
1011
|
case "studio-close-file-tab":
|
|
930
1012
|
await closeStudioFileTab(element.dataset.path);
|
|
931
1013
|
return;
|
|
932
1014
|
|
|
933
1015
|
case "studio-toggle-folder":
|
|
934
|
-
toggleStudioFolder(element.dataset.folder);
|
|
1016
|
+
await toggleStudioFolder(element.dataset.folder);
|
|
935
1017
|
return;
|
|
936
1018
|
|
|
937
1019
|
case "studio-toggle-terminal":
|
|
@@ -958,6 +1040,10 @@ async function runAction(name, element) {
|
|
|
958
1040
|
await runStudioCheck();
|
|
959
1041
|
return;
|
|
960
1042
|
|
|
1043
|
+
case "studio-package-size":
|
|
1044
|
+
await refreshStudioPackageSize({ force: true, notify: true });
|
|
1045
|
+
return;
|
|
1046
|
+
|
|
961
1047
|
case "studio-toggle-theme":
|
|
962
1048
|
toggleStudioTheme();
|
|
963
1049
|
return;
|
|
@@ -1075,7 +1161,14 @@ async function runAction(name, element) {
|
|
|
1075
1161
|
return;
|
|
1076
1162
|
|
|
1077
1163
|
case "studio-release-refresh":
|
|
1078
|
-
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();
|
|
1079
1172
|
return;
|
|
1080
1173
|
|
|
1081
1174
|
case "studio-release-switch":
|
|
@@ -1437,6 +1530,7 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1437
1530
|
id,
|
|
1438
1531
|
plugin: null,
|
|
1439
1532
|
files: [],
|
|
1533
|
+
directories: [],
|
|
1440
1534
|
selectedFile: null,
|
|
1441
1535
|
fileContent: "",
|
|
1442
1536
|
draftContent: "",
|
|
@@ -1457,7 +1551,12 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1457
1551
|
openFiles: [],
|
|
1458
1552
|
terminalOpen: true,
|
|
1459
1553
|
collapsedFolders: new Set(),
|
|
1460
|
-
|
|
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
|
+
],
|
|
1461
1560
|
aiPrompt: "",
|
|
1462
1561
|
aiJobId: null,
|
|
1463
1562
|
aiRunning: false,
|
|
@@ -1475,6 +1574,12 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1475
1574
|
sidebarTab,
|
|
1476
1575
|
playgroundVersionModal: null,
|
|
1477
1576
|
release: createInitialStudioRelease(),
|
|
1577
|
+
ignoreState: createInitialStudioIgnoreState(),
|
|
1578
|
+
packageSize: createInitialStudioPackageSize(),
|
|
1579
|
+
ignoreLoading: false,
|
|
1580
|
+
ignoreError: "",
|
|
1581
|
+
ignoreBusyPattern: "",
|
|
1582
|
+
ignoreBusyPath: "",
|
|
1478
1583
|
pendingConfirms: new Map()
|
|
1479
1584
|
};
|
|
1480
1585
|
saveStudioSidebarTab(pluginKey, sidebarTab);
|
|
@@ -1490,12 +1595,15 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1490
1595
|
state.studio.loading = false;
|
|
1491
1596
|
|
|
1492
1597
|
if (scope === "local") {
|
|
1493
|
-
const [result, checkState] = await Promise.all([
|
|
1598
|
+
const [result, checkState, ignoreState] = await Promise.all([
|
|
1494
1599
|
api(`/api/plugins/local/${encodeURIComponent(id)}/files`),
|
|
1495
|
-
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())
|
|
1496
1602
|
]);
|
|
1497
1603
|
state.studio.files = result.files ?? [];
|
|
1604
|
+
state.studio.directories = result.directories ?? [];
|
|
1498
1605
|
applyStudioCheckState(checkState.state);
|
|
1606
|
+
applyStudioIgnoreState(ignoreState);
|
|
1499
1607
|
renderStudio();
|
|
1500
1608
|
const requestedFile = options.filePath
|
|
1501
1609
|
? state.studio.files.find((file) => file.path === options.filePath)
|
|
@@ -1516,6 +1624,7 @@ async function openStudio(scope, id, options = {}) {
|
|
|
1516
1624
|
updateRouteFromState({ replace: options.replaceRoute });
|
|
1517
1625
|
}
|
|
1518
1626
|
}
|
|
1627
|
+
void refreshStudioPackageSize({ render: true });
|
|
1519
1628
|
} else {
|
|
1520
1629
|
state.studio.files = [{ path: "readme.txt", name: "readme.txt", directory: "", size: detail.readme?.length ?? 0 }];
|
|
1521
1630
|
state.studio.selectedFile = state.studio.files[0];
|
|
@@ -1587,7 +1696,88 @@ async function selectStudioFile(relativePath, options = {}) {
|
|
|
1587
1696
|
remountStudioEditorIfNeeded();
|
|
1588
1697
|
} catch (error) {
|
|
1589
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;
|
|
1590
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;
|
|
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;
|
|
1591
1781
|
}
|
|
1592
1782
|
}
|
|
1593
1783
|
|
|
@@ -1623,6 +1813,10 @@ async function saveStudioFile() {
|
|
|
1623
1813
|
state.studio.draftContent = content;
|
|
1624
1814
|
state.studio.dirty = false;
|
|
1625
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
|
+
}
|
|
1626
1820
|
renderStudio();
|
|
1627
1821
|
remountStudioEditorIfNeeded();
|
|
1628
1822
|
updateStudioControls();
|
|
@@ -1887,6 +2081,10 @@ async function acceptStudioAiChange(filePath) {
|
|
|
1887
2081
|
applyStudioCheckState(result.checkState);
|
|
1888
2082
|
}
|
|
1889
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
|
+
}
|
|
1890
2088
|
removeStudioAiChangedFile(change.path);
|
|
1891
2089
|
if (state.studio.selectedFile?.path === change.path) {
|
|
1892
2090
|
if (change.status === "deleted") {
|
|
@@ -2047,8 +2245,9 @@ function renderStudio() {
|
|
|
2047
2245
|
return;
|
|
2048
2246
|
}
|
|
2049
2247
|
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2248
|
+
const hasExplorerEntries = state.studio.files.length || state.studio.directories.length;
|
|
2249
|
+
const fileList = hasExplorerEntries
|
|
2250
|
+
? renderStudioFileTree(buildStudioFileTree(state.studio.files, state.studio.directories))
|
|
2052
2251
|
: `<p class="studio-muted">No editable text files found.</p>`;
|
|
2053
2252
|
const playgroundPort = state.studio.playgroundUrl ? new URL(state.studio.playgroundUrl).port : "";
|
|
2054
2253
|
const panels = normalizeStudioPanelState(state.studio.panels);
|
|
@@ -2061,30 +2260,37 @@ function renderStudio() {
|
|
|
2061
2260
|
<span>${escapeHtml(source)}</span>
|
|
2062
2261
|
</div>
|
|
2063
2262
|
<div class="studio-title-actions">
|
|
2064
|
-
<
|
|
2065
|
-
<
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
</
|
|
2076
|
-
<
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
</
|
|
2083
|
-
<
|
|
2084
|
-
<
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
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">
|
|
2276
|
+
${renderStudioPlayButton()}
|
|
2277
|
+
<span class="studio-preview-state${state.studio.running ? " is-loading" : state.studio.playgroundUrl ? " is-ready" : ""}" title="${escapeAttr(studioPreviewStateTitle())}">
|
|
2278
|
+
<span aria-hidden="true"></span>
|
|
2279
|
+
<em>${escapeHtml(studioPreviewStateLabel())}</em>
|
|
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>
|
|
2088
2294
|
</div>
|
|
2089
2295
|
</header>
|
|
2090
2296
|
<div class="studio-main">
|
|
@@ -2191,7 +2397,7 @@ function renderStudioActivityBar(panels) {
|
|
|
2191
2397
|
})}
|
|
2192
2398
|
${activityButton({
|
|
2193
2399
|
action: "studio-open-sidebar-tab",
|
|
2194
|
-
icon: "
|
|
2400
|
+
icon: "ps-icon-rocket",
|
|
2195
2401
|
label: "Release",
|
|
2196
2402
|
active: panels.sidebar && tab === "release",
|
|
2197
2403
|
tab: "release"
|
|
@@ -2478,6 +2684,68 @@ function renderStudioPlayButton() {
|
|
|
2478
2684
|
`;
|
|
2479
2685
|
}
|
|
2480
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
|
+
|
|
2481
2749
|
function renderStudioThemeToggle() {
|
|
2482
2750
|
const theme = studioTheme();
|
|
2483
2751
|
const isLight = theme === "light";
|
|
@@ -2494,13 +2762,23 @@ function renderStudioThemeToggle() {
|
|
|
2494
2762
|
}
|
|
2495
2763
|
|
|
2496
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() {
|
|
2497
2775
|
if (state.studio.running) {
|
|
2498
2776
|
return "Starting Playground";
|
|
2499
2777
|
}
|
|
2500
2778
|
if (state.studio.playgroundUrl) {
|
|
2501
2779
|
return "Playground ready";
|
|
2502
2780
|
}
|
|
2503
|
-
return "
|
|
2781
|
+
return "Playground not started";
|
|
2504
2782
|
}
|
|
2505
2783
|
|
|
2506
2784
|
function studioEditorStatusLabel() {
|
|
@@ -2618,22 +2896,133 @@ function toggleStudioTheme() {
|
|
|
2618
2896
|
requestAnimationFrame(() => state.studio.editor?.layout?.());
|
|
2619
2897
|
}
|
|
2620
2898
|
|
|
2621
|
-
function toggleStudioFolder(folderPath) {
|
|
2899
|
+
async function toggleStudioFolder(folderPath) {
|
|
2622
2900
|
if (!folderPath) {
|
|
2623
2901
|
return;
|
|
2624
2902
|
}
|
|
2903
|
+
const normalized = String(folderPath ?? "").replace(/\/+$/, "");
|
|
2625
2904
|
captureStudioEditorValue();
|
|
2626
|
-
|
|
2627
|
-
|
|
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);
|
|
2628
2936
|
} else {
|
|
2629
|
-
state.studio.collapsedFolders.add(
|
|
2937
|
+
state.studio.collapsedFolders.add(normalized);
|
|
2938
|
+
}
|
|
2939
|
+
renderStudio();
|
|
2940
|
+
remountStudioEditorIfNeeded();
|
|
2941
|
+
}
|
|
2942
|
+
|
|
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;
|
|
2630
2947
|
}
|
|
2948
|
+
if (state.studio.loadingFolders.has(normalized)) {
|
|
2949
|
+
return false;
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
state.studio.loadingFolders.add(normalized);
|
|
2631
2953
|
renderStudio();
|
|
2632
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);
|
|
2633
2977
|
}
|
|
2634
2978
|
|
|
2635
|
-
function
|
|
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 = []) {
|
|
2636
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
|
+
|
|
2637
3026
|
for (const file of files) {
|
|
2638
3027
|
const parts = file.path.split("/").filter(Boolean);
|
|
2639
3028
|
let node = root;
|
|
@@ -2677,19 +3066,48 @@ function renderStudioTreeChildren(children, depth) {
|
|
|
2677
3066
|
|
|
2678
3067
|
function renderStudioTreeNode(node, depth) {
|
|
2679
3068
|
if (node.type === "folder") {
|
|
2680
|
-
const
|
|
3069
|
+
const deferred = Boolean(node.deferred);
|
|
3070
|
+
const loadingFolder = state.studio.loadingFolders?.has(node.path);
|
|
3071
|
+
const nodeIgnored = Boolean(node.ignored);
|
|
2681
3072
|
const containsCurrent = Boolean(
|
|
2682
3073
|
state.studio.selectedFile?.path && state.studio.selectedFile.path.startsWith(`${node.path}/`)
|
|
2683
3074
|
);
|
|
2684
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";
|
|
2685
3089
|
return `
|
|
2686
|
-
<div class="studio-tree-folder${containsCurrent ? " has-current" : ""}${containsAiChange ? " has-ai-changes" : ""}" role="treeitem" aria-expanded="${collapsed ? "false" : "true"}">
|
|
2687
|
-
<
|
|
2688
|
-
<
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
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>
|
|
2693
3111
|
${collapsed ? "" : `<div role="group">${renderStudioTreeChildren(node.children, depth + 1)}</div>`}
|
|
2694
3112
|
</div>
|
|
2695
3113
|
`;
|
|
@@ -2698,6 +3116,12 @@ function renderStudioTreeNode(node, depth) {
|
|
|
2698
3116
|
const current = node.path === state.studio.selectedFile?.path;
|
|
2699
3117
|
const checkCounts = studioCheckCountsForPath(node.path);
|
|
2700
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;
|
|
2701
3125
|
const checkBadge = checkCounts.total
|
|
2702
3126
|
? `<span class="studio-tree-check-badge${checkCounts.error ? " has-errors" : ""}" title="${escapeAttr(formatCheckCounts(checkCounts))}">${escapeHtml(String(checkCounts.total))}</span>`
|
|
2703
3127
|
: "";
|
|
@@ -2705,11 +3129,43 @@ function renderStudioTreeNode(node, depth) {
|
|
|
2705
3129
|
? `<span class="studio-tree-ai-badge" title="${escapeAttr(`AI proposed ${aiChange.status} patch`)}">AI</span>`
|
|
2706
3130
|
: "";
|
|
2707
3131
|
return `
|
|
2708
|
-
<
|
|
2709
|
-
<
|
|
2710
|
-
|
|
2711
|
-
|
|
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>
|
|
2712
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>
|
|
2713
3169
|
</button>
|
|
2714
3170
|
`;
|
|
2715
3171
|
}
|
|
@@ -2738,7 +3194,7 @@ function renderStudioSidebarTabs(activeTab) {
|
|
|
2738
3194
|
AI Helper
|
|
2739
3195
|
</button>
|
|
2740
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">
|
|
2741
|
-
<span class="dashicons
|
|
3197
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
2742
3198
|
Release
|
|
2743
3199
|
</button>
|
|
2744
3200
|
</div>
|
|
@@ -3328,6 +3784,135 @@ function appendStudioTerminal(message, tone = "muted") {
|
|
|
3328
3784
|
}
|
|
3329
3785
|
}
|
|
3330
3786
|
|
|
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
|
+
|
|
3331
3916
|
function selectedStudioAiAssistant() {
|
|
3332
3917
|
return state.settings?.aiAssistant ?? "none";
|
|
3333
3918
|
}
|
|
@@ -3393,6 +3978,287 @@ function applyStudioCheckState(checkState) {
|
|
|
3393
3978
|
state.studio.checkRanAt = checkState.checkedAt ?? null;
|
|
3394
3979
|
}
|
|
3395
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}/**` : "";
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
function studioIgnoredFilesForPrefix(prefix) {
|
|
4168
|
+
const normalized = String(prefix ?? "").replace(/\/+$/, "");
|
|
4169
|
+
if (!normalized) {
|
|
4170
|
+
return [];
|
|
4171
|
+
}
|
|
4172
|
+
|
|
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
|
+
}
|
|
4184
|
+
};
|
|
4185
|
+
|
|
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
|
+
}
|
|
4194
|
+
|
|
4195
|
+
return files;
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
async function addStudioIgnoreRule(pattern, options = {}) {
|
|
4199
|
+
if (!pattern || state.studio.scope !== "local" || !state.studio.id) {
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
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();
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
async function removeStudioIgnoreRule(pattern) {
|
|
4232
|
+
if (!pattern || state.studio.scope !== "local" || !state.studio.id) {
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
|
|
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
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
|
|
3396
4262
|
function appendStudioAiMessage(role, text, tone = "muted") {
|
|
3397
4263
|
state.studio.aiMessages.push({
|
|
3398
4264
|
role,
|
|
@@ -3728,6 +4594,16 @@ function chooseInitialStudioFile(files, slug) {
|
|
|
3728
4594
|
);
|
|
3729
4595
|
}
|
|
3730
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
|
+
|
|
3731
4607
|
function updateStudioControls() {
|
|
3732
4608
|
const canRun = Boolean(state.studio.id) && !state.studio.loading && !state.studio.running;
|
|
3733
4609
|
const canCheck =
|
|
@@ -3749,12 +4625,17 @@ function updateStudioControls() {
|
|
|
3749
4625
|
}
|
|
3750
4626
|
if (studioCheck) {
|
|
3751
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";
|
|
3752
4630
|
studioCheck.innerHTML = state.studio.checking
|
|
3753
|
-
? `<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
3754
|
-
: `<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>`;
|
|
3755
4633
|
}
|
|
3756
4634
|
if (studioSave) {
|
|
3757
|
-
studioSave.disabled =
|
|
4635
|
+
studioSave.disabled = !canSaveStudioFile();
|
|
4636
|
+
studioSave.title = `Save ${studioSaveShortcutLabel()}`;
|
|
4637
|
+
studioSave.setAttribute("aria-label", `Save ${studioSaveShortcutLabel()}`);
|
|
4638
|
+
studioSave.setAttribute("aria-keyshortcuts", studioSaveAriaShortcut());
|
|
3758
4639
|
}
|
|
3759
4640
|
updateStudioAiControls();
|
|
3760
4641
|
}
|
|
@@ -3846,6 +4727,11 @@ async function mountStudioEditor(content) {
|
|
|
3846
4727
|
scrollBeyondLastLine: false
|
|
3847
4728
|
});
|
|
3848
4729
|
state.studio.editorKind = "monaco";
|
|
4730
|
+
state.studio.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
4731
|
+
if (canSaveStudioFile()) {
|
|
4732
|
+
void saveStudioFile();
|
|
4733
|
+
}
|
|
4734
|
+
});
|
|
3849
4735
|
state.studio.editor.onDidChangeModelContent(() => {
|
|
3850
4736
|
state.studio.draftContent = getStudioEditorValue();
|
|
3851
4737
|
state.studio.dirty = state.studio.draftContent !== state.studio.fileContent;
|
|
@@ -4057,6 +4943,7 @@ function getStudioEditorValue() {
|
|
|
4057
4943
|
|
|
4058
4944
|
function ensureMonaco() {
|
|
4059
4945
|
if (window.monaco) {
|
|
4946
|
+
configurePressshipMonaco(window.monaco);
|
|
4060
4947
|
return Promise.resolve(window.monaco);
|
|
4061
4948
|
}
|
|
4062
4949
|
if (monacoPromise) {
|
|
@@ -4072,7 +4959,10 @@ function ensureMonaco() {
|
|
|
4072
4959
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"
|
|
4073
4960
|
}
|
|
4074
4961
|
});
|
|
4075
|
-
window.require(["vs/editor/editor.main"], () =>
|
|
4962
|
+
window.require(["vs/editor/editor.main"], () => {
|
|
4963
|
+
configurePressshipMonaco(window.monaco);
|
|
4964
|
+
resolve(window.monaco);
|
|
4965
|
+
}, reject);
|
|
4076
4966
|
};
|
|
4077
4967
|
script.onerror = () => reject(new Error("Could not load Monaco Editor."));
|
|
4078
4968
|
document.head.appendChild(script);
|
|
@@ -4081,7 +4971,132 @@ function ensureMonaco() {
|
|
|
4081
4971
|
return monacoPromise;
|
|
4082
4972
|
}
|
|
4083
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
|
+
|
|
4084
5095
|
function languageForPath(filePath) {
|
|
5096
|
+
const fileName = String(filePath ?? "").split("/").pop()?.toLowerCase();
|
|
5097
|
+
if (fileName === "readme.txt") {
|
|
5098
|
+
return "wordpress-readme";
|
|
5099
|
+
}
|
|
4085
5100
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
4086
5101
|
const map = {
|
|
4087
5102
|
css: "css",
|
|
@@ -4634,7 +5649,7 @@ function localCard(plugin, versionState) {
|
|
|
4634
5649
|
Details
|
|
4635
5650
|
</button>
|
|
4636
5651
|
<button type="button" role="menuitem" data-action="manage-release" data-id="${escapeAttr(plugin.id)}">
|
|
4637
|
-
<span class="dashicons
|
|
5652
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
4638
5653
|
Manage release
|
|
4639
5654
|
</button>
|
|
4640
5655
|
<button type="button" role="menuitem" data-action="version-state" data-id="${escapeAttr(plugin.id)}">
|
|
@@ -4670,7 +5685,7 @@ function localCard(plugin, versionState) {
|
|
|
4670
5685
|
Open in Studio
|
|
4671
5686
|
</button>
|
|
4672
5687
|
<button type="button" class="ps-plugin-card-secondary" data-action="manage-release" data-id="${escapeAttr(plugin.id)}" ${missing ? "disabled" : ""}>
|
|
4673
|
-
<span class="dashicons
|
|
5688
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
4674
5689
|
Manage release
|
|
4675
5690
|
</button>
|
|
4676
5691
|
</footer>
|
|
@@ -4683,6 +5698,12 @@ function localCard(plugin, versionState) {
|
|
|
4683
5698
|
* =================================================================== */
|
|
4684
5699
|
|
|
4685
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
|
+
}
|
|
4686
5707
|
els.detail.classList.add("is-open");
|
|
4687
5708
|
els.detail.setAttribute("aria-hidden", "false");
|
|
4688
5709
|
els.detail.innerHTML = `
|
|
@@ -4785,6 +5806,7 @@ function closeDetail() {
|
|
|
4785
5806
|
* =================================================================== */
|
|
4786
5807
|
|
|
4787
5808
|
async function createJob(body) {
|
|
5809
|
+
appendStudioCliCommand(studioCliCommandForJob(body));
|
|
4788
5810
|
const job = await api("/api/jobs", { method: "POST", body });
|
|
4789
5811
|
upsertJob(job);
|
|
4790
5812
|
subscribeJob(job.id);
|
|
@@ -5587,7 +6609,7 @@ function commandItems() {
|
|
|
5587
6609
|
id: "view:release",
|
|
5588
6610
|
title: "Go to Release Management",
|
|
5589
6611
|
subtitle: "Release status for every local plugin",
|
|
5590
|
-
icon: "
|
|
6612
|
+
icon: "ps-icon-rocket",
|
|
5591
6613
|
run: () => showView("release")
|
|
5592
6614
|
},
|
|
5593
6615
|
{
|
|
@@ -5647,7 +6669,7 @@ function commandItems() {
|
|
|
5647
6669
|
id: `release:${plugin.id}`,
|
|
5648
6670
|
title: `Manage release • ${plugin.name}`,
|
|
5649
6671
|
subtitle: plugin.slug,
|
|
5650
|
-
icon: "
|
|
6672
|
+
icon: "ps-icon-rocket",
|
|
5651
6673
|
run: () => {
|
|
5652
6674
|
void openStudio("local", plugin.id, { sidebarTab: "release" });
|
|
5653
6675
|
}
|
|
@@ -6203,6 +7225,22 @@ function isSafeMarkdownHref(value) {
|
|
|
6203
7225
|
}
|
|
6204
7226
|
}
|
|
6205
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
|
+
|
|
6206
7244
|
function escapeHtml(value) {
|
|
6207
7245
|
return String(value)
|
|
6208
7246
|
.replaceAll("&", "&")
|
|
@@ -6315,7 +7353,7 @@ function renderReleaseBoard() {
|
|
|
6315
7353
|
els.release.innerHTML = emptyState({
|
|
6316
7354
|
title: "No local plugins to release.",
|
|
6317
7355
|
message: "Add a plugin folder to your Local Library, then come back here.",
|
|
6318
|
-
icon: "
|
|
7356
|
+
icon: "ps-icon-rocket"
|
|
6319
7357
|
});
|
|
6320
7358
|
return;
|
|
6321
7359
|
}
|
|
@@ -6324,7 +7362,7 @@ function renderReleaseBoard() {
|
|
|
6324
7362
|
els.release.innerHTML = `
|
|
6325
7363
|
<div class="ps-card-toolbar" role="region" aria-label="Release board summary">
|
|
6326
7364
|
<span class="ps-card-toolbar-count">
|
|
6327
|
-
<span class="dashicons
|
|
7365
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6328
7366
|
${escapeHtml(
|
|
6329
7367
|
`${state.releaseBoard.plugins.length} plugin${state.releaseBoard.plugins.length === 1 ? "" : "s"} tracked`
|
|
6330
7368
|
)}
|
|
@@ -6367,7 +7405,7 @@ function releaseBoardCard(entry) {
|
|
|
6367
7405
|
: ""}
|
|
6368
7406
|
<footer class="ps-release-board-card-footer">
|
|
6369
7407
|
<button type="button" class="button button-primary" data-action="manage-release" data-id="${escapeAttr(entry.id)}" ${entry.exists === false ? "disabled" : ""}>
|
|
6370
|
-
<span class="dashicons
|
|
7408
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6371
7409
|
Manage release
|
|
6372
7410
|
</button>
|
|
6373
7411
|
<button type="button" class="ps-plugin-card-secondary" data-action="version-state" data-id="${escapeAttr(entry.id)}" ${entry.exists === false ? "disabled" : ""}>
|
|
@@ -6398,6 +7436,9 @@ function setStudioSidebarTab(tab) {
|
|
|
6398
7436
|
if (next === "release" && state.studio.scope === "local" && !state.studio.release.tags) {
|
|
6399
7437
|
void loadStudioReleaseTags();
|
|
6400
7438
|
}
|
|
7439
|
+
if (next === "release" && state.studio.scope === "local") {
|
|
7440
|
+
void refreshStudioIgnoreState({ files: true });
|
|
7441
|
+
}
|
|
6401
7442
|
}
|
|
6402
7443
|
|
|
6403
7444
|
function updateStudioSidebar() {
|
|
@@ -6422,7 +7463,7 @@ function renderStudioReleasePane() {
|
|
|
6422
7463
|
return `
|
|
6423
7464
|
<div class="studio-release-pane">
|
|
6424
7465
|
<div class="studio-release-empty">
|
|
6425
|
-
<span class="dashicons
|
|
7466
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6426
7467
|
<strong>Release management is local-only</strong>
|
|
6427
7468
|
<p>Open a local plugin to manage its release lifecycle.</p>
|
|
6428
7469
|
</div>
|
|
@@ -6433,7 +7474,7 @@ function renderStudioReleasePane() {
|
|
|
6433
7474
|
return `
|
|
6434
7475
|
<div class="studio-release-pane">
|
|
6435
7476
|
<div class="studio-release-empty">
|
|
6436
|
-
<span class="dashicons
|
|
7477
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6437
7478
|
<strong>Open a plugin to start</strong>
|
|
6438
7479
|
</div>
|
|
6439
7480
|
</div>
|
|
@@ -6457,6 +7498,7 @@ function renderStudioReleasePane() {
|
|
|
6457
7498
|
${renderStudioReleaseStepVersion(versionState, release)}
|
|
6458
7499
|
${renderStudioReleaseStepTags(versionState, release)}
|
|
6459
7500
|
${renderStudioReleaseStepValidate(versionState, release)}
|
|
7501
|
+
${renderStudioReleaseStepIgnored()}
|
|
6460
7502
|
${renderStudioReleaseStepPublish(versionState, release)}
|
|
6461
7503
|
</ol>
|
|
6462
7504
|
</div>
|
|
@@ -6479,6 +7521,16 @@ function renderStudioReleaseStepShell(number, title, summary, body) {
|
|
|
6479
7521
|
`;
|
|
6480
7522
|
}
|
|
6481
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
|
+
|
|
6482
7534
|
function renderStudioReleaseStepVersion(versionState, release) {
|
|
6483
7535
|
const localVersion = versionState?.localVersion ?? "—";
|
|
6484
7536
|
const stable = versionState?.readmeStableTag ?? "—";
|
|
@@ -6694,6 +7746,72 @@ function renderStudioReleaseStepValidate(versionState, release) {
|
|
|
6694
7746
|
return renderStudioReleaseStepShell(3, "Validate", summaryLine, body);
|
|
6695
7747
|
}
|
|
6696
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
|
+
|
|
6697
7815
|
function studioPublishRouteDetails(action) {
|
|
6698
7816
|
switch (action) {
|
|
6699
7817
|
case "submit":
|
|
@@ -6710,7 +7828,7 @@ function studioPublishRouteDetails(action) {
|
|
|
6710
7828
|
return {
|
|
6711
7829
|
label: "Release update",
|
|
6712
7830
|
shortLabel: "release",
|
|
6713
|
-
icon: "
|
|
7831
|
+
icon: "ps-icon-rocket",
|
|
6714
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.",
|
|
6715
7833
|
dryRunLabel: "Dry-run release",
|
|
6716
7834
|
confirmLabel: "Confirm release",
|
|
@@ -6833,7 +7951,7 @@ function renderStudioReleaseStepPublish(versionState, release) {
|
|
|
6833
7951
|
? "Blocked — fix version state before publishing"
|
|
6834
7952
|
: "";
|
|
6835
7953
|
|
|
6836
|
-
return renderStudioReleaseStepShell(
|
|
7954
|
+
return renderStudioReleaseStepShell(5, "Submit / Release", summary, body);
|
|
6837
7955
|
}
|
|
6838
7956
|
|
|
6839
7957
|
/* ===================================================================
|
|
@@ -6870,16 +7988,19 @@ async function refreshStudioAfterReleaseSwitch(result = {}) {
|
|
|
6870
7988
|
const localId = state.studio.id;
|
|
6871
7989
|
const selectedPath = state.studio.selectedFile?.path;
|
|
6872
7990
|
try {
|
|
6873
|
-
const [detail, filesResult, checkState, versionState] = await Promise.all([
|
|
7991
|
+
const [detail, filesResult, checkState, versionState, ignoreState] = await Promise.all([
|
|
6874
7992
|
api(`/api/plugins/local/${encodeURIComponent(localId)}`),
|
|
6875
7993
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/files`),
|
|
6876
7994
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/check-state`).catch(() => ({ state: null })),
|
|
6877
|
-
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())
|
|
6878
7997
|
]);
|
|
6879
7998
|
|
|
6880
7999
|
applyStudioPluginDetail("local", localId, detail);
|
|
6881
8000
|
state.studio.files = filesResult.files ?? [];
|
|
8001
|
+
state.studio.directories = filesResult.directories ?? [];
|
|
6882
8002
|
applyStudioCheckState(checkState.state);
|
|
8003
|
+
applyStudioIgnoreState(ignoreState);
|
|
6883
8004
|
if (versionState) {
|
|
6884
8005
|
state.versionStates.set(localId, versionState);
|
|
6885
8006
|
renderLocal();
|
|
@@ -6917,16 +8038,19 @@ async function refreshStudioAfterVersionChange(localId) {
|
|
|
6917
8038
|
|
|
6918
8039
|
const selectedPath = state.studio.selectedFile?.path;
|
|
6919
8040
|
try {
|
|
6920
|
-
const [detail, filesResult, checkState, versionState] = await Promise.all([
|
|
8041
|
+
const [detail, filesResult, checkState, versionState, ignoreState] = await Promise.all([
|
|
6921
8042
|
api(`/api/plugins/local/${encodeURIComponent(localId)}`),
|
|
6922
8043
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/files`),
|
|
6923
8044
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/check-state`).catch(() => ({ state: null })),
|
|
6924
|
-
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null)
|
|
8045
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null),
|
|
8046
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/ignore-state`).catch(() => createInitialStudioIgnoreState())
|
|
6925
8047
|
]);
|
|
6926
8048
|
|
|
6927
8049
|
applyStudioPluginDetail("local", localId, detail);
|
|
6928
8050
|
state.studio.files = filesResult.files ?? [];
|
|
8051
|
+
state.studio.directories = filesResult.directories ?? [];
|
|
6929
8052
|
applyStudioCheckState(checkState.state);
|
|
8053
|
+
applyStudioIgnoreState(ignoreState);
|
|
6930
8054
|
if (versionState) {
|
|
6931
8055
|
state.versionStates.set(localId, versionState);
|
|
6932
8056
|
if (!state.studio.release.newTagDraft || state.studio.release.newTagDraft === versionState.latestSvnTag) {
|
|
@@ -7025,6 +8149,7 @@ async function switchStudioReleaseTag(tag, resolution) {
|
|
|
7025
8149
|
|
|
7026
8150
|
async function bumpStudioReleaseVersion(localId, bump) {
|
|
7027
8151
|
if (!localId || !["patch", "minor", "major"].includes(bump)) return;
|
|
8152
|
+
appendStudioCliCommand(studioCliCommand(["version", bump, localPluginCliTarget(localId)]));
|
|
7028
8153
|
if (state.studio.id !== localId) {
|
|
7029
8154
|
// Outside the funnel (e.g. invoked from a command palette). Run without
|
|
7030
8155
|
// the inline busy state — the global notice is enough feedback.
|