pressship 0.1.14 → 0.2.0
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/README.md +9 -6
- package/assets/web/app.js +1276 -79
- package/assets/web/style.css +663 -79
- package/composer.json +3 -3
- 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 }) => {
|
|
@@ -72,14 +76,14 @@ const HARNESS_ICON = {
|
|
|
72
76
|
const STUDIO_LAYOUT_STORAGE_KEY = "pressship.studio.layout.v1";
|
|
73
77
|
|
|
74
78
|
const STUDIO_LAYOUT_DEFAULTS = {
|
|
75
|
-
files:
|
|
79
|
+
files: 220,
|
|
76
80
|
ai: 330,
|
|
77
81
|
terminal: 190,
|
|
78
82
|
checkNotes: 152
|
|
79
83
|
};
|
|
80
84
|
|
|
81
85
|
const STUDIO_LAYOUT_LIMITS = {
|
|
82
|
-
files: { min:
|
|
86
|
+
files: { min: 160, max: 440 },
|
|
83
87
|
ai: { min: 260, max: 720 },
|
|
84
88
|
terminal: { min: 100, max: 600 },
|
|
85
89
|
checkNotes: { min: 80, max: 520 }
|
|
@@ -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;
|
|
2947
|
+
}
|
|
2948
|
+
if (state.studio.loadingFolders.has(normalized)) {
|
|
2949
|
+
return false;
|
|
2630
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);
|
|
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);
|
|
2633
3000
|
}
|
|
2634
3001
|
|
|
2635
|
-
function buildStudioFileTree(files) {
|
|
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>
|
|
@@ -3005,15 +3461,19 @@ function renderStudioAiChangeCard(change) {
|
|
|
3005
3461
|
<article class="studio-ai-change-card${selected ? " is-selected" : ""}">
|
|
3006
3462
|
<button type="button" class="studio-ai-change" data-action="studio-ai-change" data-path="${escapeAttr(change.path)}">
|
|
3007
3463
|
<span class="dashicons ${change.status === "deleted" ? "dashicons-trash" : studioFileIcon(change.path)}" aria-hidden="true"></span>
|
|
3008
|
-
<span
|
|
3009
|
-
|
|
3464
|
+
<span class="studio-ai-change-main">
|
|
3465
|
+
<span class="studio-ai-change-path">${escapeHtml(change.path)}</span>
|
|
3466
|
+
<small>${escapeHtml(studioAiPatchSummary(change))}</small>
|
|
3467
|
+
</span>
|
|
3010
3468
|
</button>
|
|
3011
3469
|
<div class="studio-ai-change-actions" aria-label="${escapeAttr(`Review ${change.path}`)}">
|
|
3012
3470
|
<button type="button" class="studio-ai-change-action is-accept" data-action="studio-ai-accept" data-path="${escapeAttr(change.path)}" title="Accept patch" aria-label="${escapeAttr(`Accept patch for ${change.path}`)}">
|
|
3013
3471
|
<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>
|
|
3472
|
+
<span>Accept</span>
|
|
3014
3473
|
</button>
|
|
3015
3474
|
<button type="button" class="studio-ai-change-action is-reject" data-action="studio-ai-reject" data-path="${escapeAttr(change.path)}" title="Reject patch" aria-label="${escapeAttr(`Reject patch for ${change.path}`)}">
|
|
3016
3475
|
<span class="dashicons dashicons-no-alt" aria-hidden="true"></span>
|
|
3476
|
+
<span>Reject</span>
|
|
3017
3477
|
</button>
|
|
3018
3478
|
</div>
|
|
3019
3479
|
</article>
|
|
@@ -3328,6 +3788,135 @@ function appendStudioTerminal(message, tone = "muted") {
|
|
|
3328
3788
|
}
|
|
3329
3789
|
}
|
|
3330
3790
|
|
|
3791
|
+
function appendStudioCliCommand(command) {
|
|
3792
|
+
if (state.activeView !== "studio" || !command || !Array.isArray(state.studio.terminal)) {
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
state.studio.terminalOpen = true;
|
|
3796
|
+
appendStudioTerminal(`$ ${command}`, "command");
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
function quoteCliArg(value) {
|
|
3800
|
+
const text = String(value ?? "").trim();
|
|
3801
|
+
if (!text) {
|
|
3802
|
+
return "''";
|
|
3803
|
+
}
|
|
3804
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(text)
|
|
3805
|
+
? text
|
|
3806
|
+
: `'${text.replace(/'/g, "'\\''")}'`;
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
function studioCliCommand(parts) {
|
|
3810
|
+
return [STUDIO_CLI_PREFIX, ...parts].filter(Boolean).join(" ");
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
function studioOpenCliCommand() {
|
|
3814
|
+
const parts = ["studio"];
|
|
3815
|
+
const host = window.location.hostname;
|
|
3816
|
+
const port = window.location.port;
|
|
3817
|
+
if (host && !["127.0.0.1", "localhost"].includes(host)) {
|
|
3818
|
+
parts.push("--host", quoteCliArg(host));
|
|
3819
|
+
}
|
|
3820
|
+
if (port && port !== "9477") {
|
|
3821
|
+
parts.push("--port", quoteCliArg(port));
|
|
3822
|
+
}
|
|
3823
|
+
return studioCliCommand(parts);
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
function localPluginForCli(localId = state.studio.id) {
|
|
3827
|
+
return state.local.find((plugin) => plugin.id === localId) ??
|
|
3828
|
+
(state.studio.scope === "local" && state.studio.id === localId ? state.studio.plugin : null);
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
function localPluginCliTarget(localId = state.studio.id) {
|
|
3832
|
+
const plugin = localPluginForCli(localId);
|
|
3833
|
+
return quoteCliArg(plugin?.path || plugin?.slug || localId || ".");
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
function studioCliIgnoreFlags(localId = state.studio.id) {
|
|
3837
|
+
if (localId !== state.studio.id) {
|
|
3838
|
+
return [];
|
|
3839
|
+
}
|
|
3840
|
+
return studioIgnorePatterns().flatMap((pattern) => ["--ignore", quoteCliArg(pattern)]);
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
function studioPublishCliCommand(localId, action, options = {}) {
|
|
3844
|
+
const normalizedAction = ["submit", "release"].includes(action) ? action : "auto";
|
|
3845
|
+
const parts = ["publish", localPluginCliTarget(localId)];
|
|
3846
|
+
if (normalizedAction !== "auto") {
|
|
3847
|
+
parts.push(`--${normalizedAction}`);
|
|
3848
|
+
}
|
|
3849
|
+
if (options.dryRun) {
|
|
3850
|
+
parts.push("--dry-run");
|
|
3851
|
+
}
|
|
3852
|
+
parts.push(...studioCliIgnoreFlags(localId), "--yes");
|
|
3853
|
+
return studioCliCommand(parts);
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
function studioPlaygroundCliCommand(input) {
|
|
3857
|
+
const target = input.scope === "local" ? localPluginCliTarget(input.id) : quoteCliArg(input.id);
|
|
3858
|
+
const settings = state.settings ?? {};
|
|
3859
|
+
const parts = ["demo", target, "--reset", "--skip-browser"];
|
|
3860
|
+
if (input.wpVersion && input.wpVersion !== "latest") {
|
|
3861
|
+
parts.push("--wp", quoteCliArg(input.wpVersion));
|
|
3862
|
+
}
|
|
3863
|
+
if (settings.playgroundDatabaseMode && settings.playgroundDatabaseMode !== "auto") {
|
|
3864
|
+
parts.push("--database", quoteCliArg(settings.playgroundDatabaseMode));
|
|
3865
|
+
}
|
|
3866
|
+
if (settings.playgroundDatabaseMode === "mysql") {
|
|
3867
|
+
parts.push(
|
|
3868
|
+
"--mysql-host",
|
|
3869
|
+
quoteCliArg(settings.playgroundMysqlHost ?? "127.0.0.1"),
|
|
3870
|
+
"--mysql-port",
|
|
3871
|
+
quoteCliArg(settings.playgroundMysqlPort ?? 3306),
|
|
3872
|
+
"--mysql-user",
|
|
3873
|
+
quoteCliArg(settings.playgroundMysqlUser ?? "root"),
|
|
3874
|
+
"--mysql-database-prefix",
|
|
3875
|
+
quoteCliArg(settings.playgroundMysqlDatabasePrefix ?? "pressship_playground")
|
|
3876
|
+
);
|
|
3877
|
+
if (settings.playgroundMysqlPassword) {
|
|
3878
|
+
parts.push("--mysql-password", quoteCliArg(settings.playgroundMysqlPassword));
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
return studioCliCommand(parts);
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
function studioCliCommandForJob(input) {
|
|
3885
|
+
if (state.activeView !== "studio") {
|
|
3886
|
+
return "";
|
|
3887
|
+
}
|
|
3888
|
+
switch (input?.type) {
|
|
3889
|
+
case "clone": {
|
|
3890
|
+
const parts = ["get", quoteCliArg(input.slug)];
|
|
3891
|
+
if (input.destination) {
|
|
3892
|
+
parts.push(quoteCliArg(input.destination));
|
|
3893
|
+
}
|
|
3894
|
+
return studioCliCommand(parts);
|
|
3895
|
+
}
|
|
3896
|
+
case "play":
|
|
3897
|
+
return studioPlaygroundCliCommand(input);
|
|
3898
|
+
case "check":
|
|
3899
|
+
return studioCliCommand([
|
|
3900
|
+
"verify",
|
|
3901
|
+
localPluginCliTarget(input.localId),
|
|
3902
|
+
"--skip-readme-validator",
|
|
3903
|
+
...studioCliIgnoreFlags(input.localId)
|
|
3904
|
+
]);
|
|
3905
|
+
case "dry-run-publish":
|
|
3906
|
+
return studioPublishCliCommand(input.localId, input.action, { dryRun: true });
|
|
3907
|
+
case "confirm-publish": {
|
|
3908
|
+
const dryRun = state.studio.release?.dryRun;
|
|
3909
|
+
if (!dryRun?.approvalId || dryRun.approvalId !== input.approvalId) {
|
|
3910
|
+
return "";
|
|
3911
|
+
}
|
|
3912
|
+
const action = dryRun?.route?.action ?? "auto";
|
|
3913
|
+
return state.studio.id ? studioPublishCliCommand(state.studio.id, action) : "";
|
|
3914
|
+
}
|
|
3915
|
+
default:
|
|
3916
|
+
return "";
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3331
3920
|
function selectedStudioAiAssistant() {
|
|
3332
3921
|
return state.settings?.aiAssistant ?? "none";
|
|
3333
3922
|
}
|
|
@@ -3393,6 +3982,287 @@ function applyStudioCheckState(checkState) {
|
|
|
3393
3982
|
state.studio.checkRanAt = checkState.checkedAt ?? null;
|
|
3394
3983
|
}
|
|
3395
3984
|
|
|
3985
|
+
function applyStudioIgnoreState(ignoreState) {
|
|
3986
|
+
state.studio.ignoreState = {
|
|
3987
|
+
...createInitialStudioIgnoreState(),
|
|
3988
|
+
...(ignoreState ?? {})
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
function applyStudioPackageSize(packageSize) {
|
|
3993
|
+
if (packageSize?.status === "calculating") {
|
|
3994
|
+
state.studio.packageSize = {
|
|
3995
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
3996
|
+
loading: true,
|
|
3997
|
+
error: "",
|
|
3998
|
+
stale: false
|
|
3999
|
+
};
|
|
4000
|
+
scheduleStudioPackageSizePoll();
|
|
4001
|
+
return;
|
|
4002
|
+
}
|
|
4003
|
+
if (packageSize?.status === "error") {
|
|
4004
|
+
state.studio.packageSize = {
|
|
4005
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
4006
|
+
loading: false,
|
|
4007
|
+
error: packageSize.error ?? "Package size could not be calculated.",
|
|
4008
|
+
calculatedAt: packageSize.calculatedAt ?? new Date().toISOString(),
|
|
4009
|
+
stale: false
|
|
4010
|
+
};
|
|
4011
|
+
return;
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
state.studio.packageSize = {
|
|
4015
|
+
...createInitialStudioPackageSize(),
|
|
4016
|
+
...(packageSize ?? {}),
|
|
4017
|
+
loading: false,
|
|
4018
|
+
error: "",
|
|
4019
|
+
calculatedAt: packageSize?.calculatedAt ?? new Date().toISOString(),
|
|
4020
|
+
stale: false
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
function markStudioPackageSizeStale() {
|
|
4025
|
+
const packageSize = state.studio.packageSize;
|
|
4026
|
+
if (!packageSize || !packageSize.calculatedAt) {
|
|
4027
|
+
return;
|
|
4028
|
+
}
|
|
4029
|
+
state.studio.packageSize = {
|
|
4030
|
+
...packageSize,
|
|
4031
|
+
stale: true
|
|
4032
|
+
};
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
async function refreshStudioPackageSize(options = {}) {
|
|
4036
|
+
if (state.studio.scope !== "local" || !state.studio.id) {
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
4039
|
+
if (state.studio.packageSize?.loading && !options.poll) {
|
|
4040
|
+
return;
|
|
4041
|
+
}
|
|
4042
|
+
if (!options.force && state.studio.packageSize?.calculatedAt) {
|
|
4043
|
+
return;
|
|
4044
|
+
}
|
|
4045
|
+
|
|
4046
|
+
const localId = state.studio.id;
|
|
4047
|
+
if (options.notify) {
|
|
4048
|
+
appendStudioCliCommand(studioCliCommand([
|
|
4049
|
+
"pack",
|
|
4050
|
+
localPluginCliTarget(localId),
|
|
4051
|
+
"--no-verify",
|
|
4052
|
+
...studioCliIgnoreFlags(localId),
|
|
4053
|
+
"--json"
|
|
4054
|
+
]));
|
|
4055
|
+
}
|
|
4056
|
+
state.studio.packageSize = {
|
|
4057
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
4058
|
+
loading: true,
|
|
4059
|
+
error: ""
|
|
4060
|
+
};
|
|
4061
|
+
if (options.render !== false) {
|
|
4062
|
+
renderStudio();
|
|
4063
|
+
remountStudioEditorIfNeeded();
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
try {
|
|
4067
|
+
const packageSize = await api(`/api/plugins/local/${encodeURIComponent(localId)}/package-size`);
|
|
4068
|
+
if (state.studio.id !== localId) {
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
applyStudioPackageSize(packageSize);
|
|
4072
|
+
if (options.notify && packageSize.status !== "calculating") {
|
|
4073
|
+
if (packageSize.status === "error") {
|
|
4074
|
+
notice(packageSize.error ?? "Package size could not be calculated.", "error");
|
|
4075
|
+
} else {
|
|
4076
|
+
notice(
|
|
4077
|
+
packageSize.overLimit
|
|
4078
|
+
? `Package is ${formatStudioBytes(packageSize.sizeBytes)}, over the WordPress.org 10 MB limit.`
|
|
4079
|
+
: `Package is ${formatStudioBytes(packageSize.sizeBytes)}.`,
|
|
4080
|
+
packageSize.overLimit ? "warning" : "success"
|
|
4081
|
+
);
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
} catch (error) {
|
|
4085
|
+
if (state.studio.id !== localId) {
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
state.studio.packageSize = {
|
|
4089
|
+
...(state.studio.packageSize ?? createInitialStudioPackageSize()),
|
|
4090
|
+
loading: false,
|
|
4091
|
+
error: error.message
|
|
4092
|
+
};
|
|
4093
|
+
if (options.notify) {
|
|
4094
|
+
notice(error.message, "error");
|
|
4095
|
+
}
|
|
4096
|
+
} finally {
|
|
4097
|
+
if (state.studio.id === localId && options.render !== false) {
|
|
4098
|
+
renderStudio();
|
|
4099
|
+
remountStudioEditorIfNeeded();
|
|
4100
|
+
updateStudioControls();
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
function scheduleStudioPackageSizePoll() {
|
|
4106
|
+
if (studioPackageSizePollTimer) {
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
studioPackageSizePollTimer = window.setTimeout(() => {
|
|
4110
|
+
studioPackageSizePollTimer = null;
|
|
4111
|
+
void refreshStudioPackageSize({ force: true, render: true, poll: true });
|
|
4112
|
+
}, 1200);
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
async function refreshStudioIgnoreState(options = {}) {
|
|
4116
|
+
if (state.studio.scope !== "local" || !state.studio.id) {
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
const localId = state.studio.id;
|
|
4121
|
+
state.studio.ignoreLoading = true;
|
|
4122
|
+
state.studio.ignoreError = "";
|
|
4123
|
+
if (options.render !== false) {
|
|
4124
|
+
updateStudioSidebar();
|
|
4125
|
+
}
|
|
4126
|
+
try {
|
|
4127
|
+
const requests = [
|
|
4128
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/ignore-state`)
|
|
4129
|
+
];
|
|
4130
|
+
if (options.files) {
|
|
4131
|
+
requests.push(api(`/api/plugins/local/${encodeURIComponent(localId)}/files`));
|
|
4132
|
+
}
|
|
4133
|
+
const [ignoreState, filesResult] = await Promise.all(requests);
|
|
4134
|
+
applyStudioIgnoreState(ignoreState);
|
|
4135
|
+
if (filesResult) {
|
|
4136
|
+
state.studio.files = filesResult.files ?? state.studio.files;
|
|
4137
|
+
state.studio.directories = filesResult.directories ?? state.studio.directories;
|
|
4138
|
+
}
|
|
4139
|
+
} catch (error) {
|
|
4140
|
+
state.studio.ignoreError = error.message;
|
|
4141
|
+
} finally {
|
|
4142
|
+
state.studio.ignoreLoading = false;
|
|
4143
|
+
state.studio.ignoreBusyPattern = "";
|
|
4144
|
+
state.studio.ignoreBusyPath = "";
|
|
4145
|
+
if (options.render !== false) {
|
|
4146
|
+
renderStudio();
|
|
4147
|
+
remountStudioEditorIfNeeded();
|
|
4148
|
+
updateStudioSidebar();
|
|
4149
|
+
updateStudioControls();
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
function studioIgnorePatterns() {
|
|
4155
|
+
return state.studio.ignoreState?.patterns ?? [];
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
function studioHasIgnorePattern(pattern) {
|
|
4159
|
+
return studioIgnorePatterns().includes(pattern);
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
function studioFileIgnorePattern(filePath) {
|
|
4163
|
+
return String(filePath ?? "").replace(/^\.\/+/, "");
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
function studioFolderIgnorePattern(folderPath) {
|
|
4167
|
+
const normalized = String(folderPath ?? "").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
4168
|
+
return normalized ? `${normalized}/**` : "";
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
function studioIgnoredFilesForPrefix(prefix) {
|
|
4172
|
+
const normalized = String(prefix ?? "").replace(/\/+$/, "");
|
|
4173
|
+
if (!normalized) {
|
|
4174
|
+
return [];
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
const files = [];
|
|
4178
|
+
const seen = new Set();
|
|
4179
|
+
const addFile = (file) => {
|
|
4180
|
+
const filePath = String(file?.path ?? "");
|
|
4181
|
+
if (!filePath || seen.has(filePath)) {
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
if (filePath === normalized || filePath.startsWith(`${normalized}/`)) {
|
|
4185
|
+
seen.add(filePath);
|
|
4186
|
+
files.push(file);
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
4189
|
+
|
|
4190
|
+
for (const file of state.studio.ignoreState?.ignoredFiles ?? []) {
|
|
4191
|
+
addFile(file);
|
|
4192
|
+
}
|
|
4193
|
+
for (const file of state.studio.files ?? []) {
|
|
4194
|
+
if (file?.ignored) {
|
|
4195
|
+
addFile(file);
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
return files;
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
async function addStudioIgnoreRule(pattern, options = {}) {
|
|
4203
|
+
if (!pattern || state.studio.scope !== "local" || !state.studio.id) {
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
state.studio.ignoreBusyPattern = pattern;
|
|
4208
|
+
state.studio.ignoreBusyPath = options.path ?? "";
|
|
4209
|
+
updateStudioSidebar();
|
|
4210
|
+
try {
|
|
4211
|
+
const ignoreState = await api(`/api/plugins/local/${encodeURIComponent(state.studio.id)}/ignore-rules`, {
|
|
4212
|
+
method: "POST",
|
|
4213
|
+
body: { pattern }
|
|
4214
|
+
});
|
|
4215
|
+
applyStudioIgnoreState(ignoreState);
|
|
4216
|
+
markStudioPackageSizeStale();
|
|
4217
|
+
await refreshStudioIgnoreState({ files: true, render: false });
|
|
4218
|
+
if (state.studio.selectedFile?.path === ".pressshipignore") {
|
|
4219
|
+
await syncSelectedStudioFileFromDisk({ reason: "ignore-rule", reportErrors: true });
|
|
4220
|
+
}
|
|
4221
|
+
appendStudioTerminal(`Ignored ${options.label ?? pattern}.`, "success");
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
appendStudioTerminal(error.message, "error");
|
|
4224
|
+
notice(error.message, "error");
|
|
4225
|
+
} finally {
|
|
4226
|
+
state.studio.ignoreBusyPattern = "";
|
|
4227
|
+
state.studio.ignoreBusyPath = "";
|
|
4228
|
+
renderStudio();
|
|
4229
|
+
remountStudioEditorIfNeeded();
|
|
4230
|
+
updateStudioSidebar();
|
|
4231
|
+
updateStudioControls();
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
async function removeStudioIgnoreRule(pattern) {
|
|
4236
|
+
if (!pattern || state.studio.scope !== "local" || !state.studio.id) {
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
state.studio.ignoreBusyPattern = pattern;
|
|
4241
|
+
updateStudioSidebar();
|
|
4242
|
+
try {
|
|
4243
|
+
const ignoreState = await api(`/api/plugins/local/${encodeURIComponent(state.studio.id)}/ignore-rules`, {
|
|
4244
|
+
method: "DELETE",
|
|
4245
|
+
body: { pattern }
|
|
4246
|
+
});
|
|
4247
|
+
applyStudioIgnoreState(ignoreState);
|
|
4248
|
+
markStudioPackageSizeStale();
|
|
4249
|
+
await refreshStudioIgnoreState({ files: true, render: false });
|
|
4250
|
+
if (state.studio.selectedFile?.path === ".pressshipignore") {
|
|
4251
|
+
await syncSelectedStudioFileFromDisk({ reason: "ignore-rule", reportErrors: true });
|
|
4252
|
+
}
|
|
4253
|
+
appendStudioTerminal(`Removed ignore rule ${pattern}.`, "success");
|
|
4254
|
+
} catch (error) {
|
|
4255
|
+
appendStudioTerminal(error.message, "error");
|
|
4256
|
+
notice(error.message, "error");
|
|
4257
|
+
} finally {
|
|
4258
|
+
state.studio.ignoreBusyPattern = "";
|
|
4259
|
+
renderStudio();
|
|
4260
|
+
remountStudioEditorIfNeeded();
|
|
4261
|
+
updateStudioSidebar();
|
|
4262
|
+
updateStudioControls();
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
|
|
3396
4266
|
function appendStudioAiMessage(role, text, tone = "muted") {
|
|
3397
4267
|
state.studio.aiMessages.push({
|
|
3398
4268
|
role,
|
|
@@ -3602,12 +4472,16 @@ function handleStudioJobEvent(id, payload) {
|
|
|
3602
4472
|
}
|
|
3603
4473
|
} else if (payload.type === "log") {
|
|
3604
4474
|
if (isAiJob) {
|
|
3605
|
-
|
|
3606
|
-
if (
|
|
4475
|
+
const aiLogData = payload.data?.data;
|
|
4476
|
+
if (aiLogData?.changedFiles) {
|
|
3607
4477
|
mergeStudioAiChangedFiles(payload.data.data.changedFiles);
|
|
3608
4478
|
updateStudioAiSidebar();
|
|
3609
4479
|
renderStudio();
|
|
3610
4480
|
remountStudioEditorIfNeeded();
|
|
4481
|
+
} else if (aiLogData?.proposedChanges) {
|
|
4482
|
+
return;
|
|
4483
|
+
} else {
|
|
4484
|
+
appendStudioAiOutput(payload.data?.message ?? payload.data, "log");
|
|
3611
4485
|
}
|
|
3612
4486
|
} else {
|
|
3613
4487
|
appendStudioTerminal(payload.data?.message ?? payload.data, "log");
|
|
@@ -3728,6 +4602,16 @@ function chooseInitialStudioFile(files, slug) {
|
|
|
3728
4602
|
);
|
|
3729
4603
|
}
|
|
3730
4604
|
|
|
4605
|
+
function canSaveStudioFile() {
|
|
4606
|
+
return Boolean(
|
|
4607
|
+
state.studio.scope === "local" &&
|
|
4608
|
+
state.studio.id &&
|
|
4609
|
+
!state.studio.readOnly &&
|
|
4610
|
+
state.studio.selectedFile &&
|
|
4611
|
+
state.studio.dirty
|
|
4612
|
+
);
|
|
4613
|
+
}
|
|
4614
|
+
|
|
3731
4615
|
function updateStudioControls() {
|
|
3732
4616
|
const canRun = Boolean(state.studio.id) && !state.studio.loading && !state.studio.running;
|
|
3733
4617
|
const canCheck =
|
|
@@ -3749,12 +4633,17 @@ function updateStudioControls() {
|
|
|
3749
4633
|
}
|
|
3750
4634
|
if (studioCheck) {
|
|
3751
4635
|
studioCheck.disabled = !canCheck;
|
|
4636
|
+
studioCheck.setAttribute("aria-label", state.studio.checkSummary ? "Re-run Plugin Check" : "Run Plugin Check");
|
|
4637
|
+
studioCheck.title = state.studio.checkSummary ? "Re-run Plugin Check" : "Run Plugin Check";
|
|
3752
4638
|
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
|
|
4639
|
+
? `<span class="dashicons dashicons-update" aria-hidden="true"></span><span>Checking</span>`
|
|
4640
|
+
: `<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><span>${state.studio.checkSummary ? "Re-check" : "Check"}</span>`;
|
|
3755
4641
|
}
|
|
3756
4642
|
if (studioSave) {
|
|
3757
|
-
studioSave.disabled =
|
|
4643
|
+
studioSave.disabled = !canSaveStudioFile();
|
|
4644
|
+
studioSave.title = `Save ${studioSaveShortcutLabel()}`;
|
|
4645
|
+
studioSave.setAttribute("aria-label", `Save ${studioSaveShortcutLabel()}`);
|
|
4646
|
+
studioSave.setAttribute("aria-keyshortcuts", studioSaveAriaShortcut());
|
|
3758
4647
|
}
|
|
3759
4648
|
updateStudioAiControls();
|
|
3760
4649
|
}
|
|
@@ -3829,6 +4718,7 @@ async function mountStudioEditor(content) {
|
|
|
3829
4718
|
modified: modifiedModel
|
|
3830
4719
|
});
|
|
3831
4720
|
state.studio.editorKind = "monaco-diff";
|
|
4721
|
+
revealStudioAiPatchChange(aiPatch);
|
|
3832
4722
|
return;
|
|
3833
4723
|
}
|
|
3834
4724
|
|
|
@@ -3846,6 +4736,11 @@ async function mountStudioEditor(content) {
|
|
|
3846
4736
|
scrollBeyondLastLine: false
|
|
3847
4737
|
});
|
|
3848
4738
|
state.studio.editorKind = "monaco";
|
|
4739
|
+
state.studio.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
4740
|
+
if (canSaveStudioFile()) {
|
|
4741
|
+
void saveStudioFile();
|
|
4742
|
+
}
|
|
4743
|
+
});
|
|
3849
4744
|
state.studio.editor.onDidChangeModelContent(() => {
|
|
3850
4745
|
state.studio.draftContent = getStudioEditorValue();
|
|
3851
4746
|
state.studio.dirty = state.studio.draftContent !== state.studio.fileContent;
|
|
@@ -3975,6 +4870,69 @@ function applyStudioAiPatchMarkers() {
|
|
|
3975
4870
|
);
|
|
3976
4871
|
}
|
|
3977
4872
|
|
|
4873
|
+
function firstStudioAiPatchLocation(change) {
|
|
4874
|
+
const hunks = change?.hunks?.length
|
|
4875
|
+
? change.hunks
|
|
4876
|
+
: change
|
|
4877
|
+
? buildStudioAiPatchHunks(change.beforeContent ?? "", change.afterContent ?? "")
|
|
4878
|
+
: [];
|
|
4879
|
+
|
|
4880
|
+
for (const hunk of hunks) {
|
|
4881
|
+
let oldLine = Number(hunk.oldStart) || 1;
|
|
4882
|
+
let newLine = Number(hunk.newStart) || 1;
|
|
4883
|
+
|
|
4884
|
+
for (const line of hunk.lines ?? []) {
|
|
4885
|
+
if (line.type === "add" || line.type === "delete") {
|
|
4886
|
+
return {
|
|
4887
|
+
oldLine: Math.max(1, oldLine),
|
|
4888
|
+
newLine: Math.max(1, newLine)
|
|
4889
|
+
};
|
|
4890
|
+
}
|
|
4891
|
+
if (line.type === "context") {
|
|
4892
|
+
oldLine += 1;
|
|
4893
|
+
newLine += 1;
|
|
4894
|
+
} else if (line.type === "add") {
|
|
4895
|
+
newLine += 1;
|
|
4896
|
+
} else if (line.type === "delete") {
|
|
4897
|
+
oldLine += 1;
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
return null;
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
function revealStudioAiPatchChange(change) {
|
|
4906
|
+
const location = firstStudioAiPatchLocation(change);
|
|
4907
|
+
if (!location) {
|
|
4908
|
+
return;
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
requestAnimationFrame(() => {
|
|
4912
|
+
if (state.studio.editorKind === "monaco-diff" && state.studio.editor) {
|
|
4913
|
+
const originalEditor = state.studio.editor.getOriginalEditor?.();
|
|
4914
|
+
const modifiedEditor = state.studio.editor.getModifiedEditor?.();
|
|
4915
|
+
const originalModel = originalEditor?.getModel?.();
|
|
4916
|
+
const modifiedModel = modifiedEditor?.getModel?.();
|
|
4917
|
+
const oldLine = originalModel ? clampEditorLine(originalModel, location.oldLine) : location.oldLine;
|
|
4918
|
+
const newLine = modifiedModel ? clampEditorLine(modifiedModel, location.newLine) : location.newLine;
|
|
4919
|
+
|
|
4920
|
+
originalEditor?.revealLineInCenter?.(oldLine);
|
|
4921
|
+
modifiedEditor?.revealLineInCenter?.(newLine);
|
|
4922
|
+
originalEditor?.setPosition?.({ lineNumber: oldLine, column: 1 });
|
|
4923
|
+
modifiedEditor?.setPosition?.({ lineNumber: newLine, column: 1 });
|
|
4924
|
+
modifiedEditor?.focus?.();
|
|
4925
|
+
return;
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
if (state.studio.editorKind === "textarea-diff" && state.studio.editor) {
|
|
4929
|
+
const offset = offsetForLineColumn(state.studio.editor.value, location.oldLine, 1);
|
|
4930
|
+
state.studio.editor.focus();
|
|
4931
|
+
state.studio.editor.setSelectionRange(offset, offset);
|
|
4932
|
+
}
|
|
4933
|
+
});
|
|
4934
|
+
}
|
|
4935
|
+
|
|
3978
4936
|
function revealStudioCheckNote(line, column = 1) {
|
|
3979
4937
|
if (!line) {
|
|
3980
4938
|
return;
|
|
@@ -4057,6 +5015,7 @@ function getStudioEditorValue() {
|
|
|
4057
5015
|
|
|
4058
5016
|
function ensureMonaco() {
|
|
4059
5017
|
if (window.monaco) {
|
|
5018
|
+
configurePressshipMonaco(window.monaco);
|
|
4060
5019
|
return Promise.resolve(window.monaco);
|
|
4061
5020
|
}
|
|
4062
5021
|
if (monacoPromise) {
|
|
@@ -4072,7 +5031,10 @@ function ensureMonaco() {
|
|
|
4072
5031
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"
|
|
4073
5032
|
}
|
|
4074
5033
|
});
|
|
4075
|
-
window.require(["vs/editor/editor.main"], () =>
|
|
5034
|
+
window.require(["vs/editor/editor.main"], () => {
|
|
5035
|
+
configurePressshipMonaco(window.monaco);
|
|
5036
|
+
resolve(window.monaco);
|
|
5037
|
+
}, reject);
|
|
4076
5038
|
};
|
|
4077
5039
|
script.onerror = () => reject(new Error("Could not load Monaco Editor."));
|
|
4078
5040
|
document.head.appendChild(script);
|
|
@@ -4081,7 +5043,132 @@ function ensureMonaco() {
|
|
|
4081
5043
|
return monacoPromise;
|
|
4082
5044
|
}
|
|
4083
5045
|
|
|
5046
|
+
function configurePressshipMonaco(monaco) {
|
|
5047
|
+
if (!monaco || monacoConfigured) {
|
|
5048
|
+
return;
|
|
5049
|
+
}
|
|
5050
|
+
monacoConfigured = true;
|
|
5051
|
+
registerWordPressReadmeLanguage(monaco);
|
|
5052
|
+
definePressshipMonacoThemes(monaco);
|
|
5053
|
+
}
|
|
5054
|
+
|
|
5055
|
+
function registerWordPressReadmeLanguage(monaco) {
|
|
5056
|
+
const languageId = "wordpress-readme";
|
|
5057
|
+
if (!monaco.languages.getLanguages().some((language) => language.id === languageId)) {
|
|
5058
|
+
monaco.languages.register({
|
|
5059
|
+
id: languageId,
|
|
5060
|
+
aliases: ["WordPress Readme", "wordpress-readme"],
|
|
5061
|
+
extensions: [".txt"],
|
|
5062
|
+
filenames: ["readme.txt"]
|
|
5063
|
+
});
|
|
5064
|
+
}
|
|
5065
|
+
|
|
5066
|
+
monaco.languages.setLanguageConfiguration(languageId, {
|
|
5067
|
+
brackets: [
|
|
5068
|
+
["[", "]"],
|
|
5069
|
+
["(", ")"],
|
|
5070
|
+
["`", "`"]
|
|
5071
|
+
],
|
|
5072
|
+
autoClosingPairs: [
|
|
5073
|
+
{ open: "`", close: "`" },
|
|
5074
|
+
{ open: "[", close: "]" },
|
|
5075
|
+
{ open: "(", close: ")" }
|
|
5076
|
+
],
|
|
5077
|
+
surroundingPairs: [
|
|
5078
|
+
{ open: "`", close: "`" },
|
|
5079
|
+
{ open: "*", close: "*" },
|
|
5080
|
+
{ open: "[", close: "]" },
|
|
5081
|
+
{ open: "(", close: ")" }
|
|
5082
|
+
],
|
|
5083
|
+
wordPattern: /(-?\d+(?:\.\d+)*)|([^\s`~!@#$%^&*()=+[{\]}\\|;:'",.<>/?]+)/g
|
|
5084
|
+
});
|
|
5085
|
+
|
|
5086
|
+
monaco.languages.setMonarchTokensProvider(languageId, {
|
|
5087
|
+
defaultToken: "wp-readme.text",
|
|
5088
|
+
tokenizer: {
|
|
5089
|
+
root: [
|
|
5090
|
+
[/^\s*={3}\s*.*?\s*={3}\s*$/, "wp-readme.title"],
|
|
5091
|
+
[/^\s*={2}\s*.*?\s*={2}\s*$/, "wp-readme.section"],
|
|
5092
|
+
[/^\s*=\s*.*?\s*=\s*$/, "wp-readme.subsection"],
|
|
5093
|
+
[/^(\s*)([*+-])(\s+)/, ["wp-readme.whitespace", "wp-readme.listMarker", "wp-readme.whitespace"]],
|
|
5094
|
+
[/^(\s*)(\d+\.)(\s+)/, ["wp-readme.whitespace", "wp-readme.listMarker", "wp-readme.whitespace"]],
|
|
5095
|
+
[/^\s{4,}.*$/, "wp-readme.codeBlock"],
|
|
5096
|
+
[/^\t.*$/, "wp-readme.codeBlock"],
|
|
5097
|
+
[/^(\s*>)(.*)$/, ["wp-readme.quote", "wp-readme.quote"]],
|
|
5098
|
+
[/^([A-Za-z][A-Za-z0-9 /.-]*)(:)(.*)$/, ["wp-readme.field", "wp-readme.delimiter", "wp-readme.fieldValue"]],
|
|
5099
|
+
[/\[[^\]\n]+\]:\s*(?:https?:\/\/|mailto:|#)[^\s]+/, "wp-readme.link"],
|
|
5100
|
+
[/\[[^\]\n]+\]\([^)]+\)/, "wp-readme.link"],
|
|
5101
|
+
[/(?:https?:\/\/|mailto:)[^\s)]+/, "wp-readme.url"],
|
|
5102
|
+
[/\b(?:pressship|npx|wp|svn|npm|composer)\s+[^\n`]+/, "wp-readme.command"],
|
|
5103
|
+
[/`[^`\n]+`/, "wp-readme.inlineCode"],
|
|
5104
|
+
[/\*\*[^*\n][\s\S]*?\*\*/, "wp-readme.strong"],
|
|
5105
|
+
[/\*[^*\s\n][^*\n]*\*/, "wp-readme.emphasis"],
|
|
5106
|
+
[/\[(?:youtube|vimeo|wpvideo|playlist|audio|video)\b[^\]\n]*\]/i, "wp-readme.shortcode"],
|
|
5107
|
+
[/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/, "wp-readme.url"]
|
|
5108
|
+
]
|
|
5109
|
+
}
|
|
5110
|
+
});
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5113
|
+
function definePressshipMonacoThemes(monaco) {
|
|
5114
|
+
monaco.editor.defineTheme("pressship-studio-dark", {
|
|
5115
|
+
base: "vs-dark",
|
|
5116
|
+
inherit: true,
|
|
5117
|
+
rules: [
|
|
5118
|
+
{ token: "wp-readme.title", foreground: "f8fafc", fontStyle: "bold" },
|
|
5119
|
+
{ token: "wp-readme.section", foreground: "7dd3fc", fontStyle: "bold" },
|
|
5120
|
+
{ token: "wp-readme.subsection", foreground: "fbbf24", fontStyle: "bold" },
|
|
5121
|
+
{ token: "wp-readme.field", foreground: "f78c6c", fontStyle: "bold" },
|
|
5122
|
+
{ token: "wp-readme.fieldValue", foreground: "cbd5e1" },
|
|
5123
|
+
{ token: "wp-readme.delimiter", foreground: "94a3b8" },
|
|
5124
|
+
{ token: "wp-readme.listMarker", foreground: "a78bfa", fontStyle: "bold" },
|
|
5125
|
+
{ token: "wp-readme.inlineCode", foreground: "c4b5fd" },
|
|
5126
|
+
{ token: "wp-readme.codeBlock", foreground: "a7f3d0" },
|
|
5127
|
+
{ token: "wp-readme.link", foreground: "93c5fd", fontStyle: "underline" },
|
|
5128
|
+
{ token: "wp-readme.url", foreground: "67e8f9", fontStyle: "underline" },
|
|
5129
|
+
{ token: "wp-readme.command", foreground: "fde68a" },
|
|
5130
|
+
{ token: "wp-readme.shortcode", foreground: "f9a8d4" },
|
|
5131
|
+
{ token: "wp-readme.quote", foreground: "9ca3af", fontStyle: "italic" },
|
|
5132
|
+
{ token: "wp-readme.strong", foreground: "f8fafc", fontStyle: "bold" },
|
|
5133
|
+
{ token: "wp-readme.emphasis", foreground: "e5e7eb", fontStyle: "italic" }
|
|
5134
|
+
],
|
|
5135
|
+
colors: {
|
|
5136
|
+
"editor.background": "#1e1e1e"
|
|
5137
|
+
}
|
|
5138
|
+
});
|
|
5139
|
+
|
|
5140
|
+
monaco.editor.defineTheme("pressship-studio-light", {
|
|
5141
|
+
base: "vs",
|
|
5142
|
+
inherit: true,
|
|
5143
|
+
rules: [
|
|
5144
|
+
{ token: "wp-readme.title", foreground: "1d4ed8", fontStyle: "bold" },
|
|
5145
|
+
{ token: "wp-readme.section", foreground: "0f766e", fontStyle: "bold" },
|
|
5146
|
+
{ token: "wp-readme.subsection", foreground: "b45309", fontStyle: "bold" },
|
|
5147
|
+
{ token: "wp-readme.field", foreground: "be123c", fontStyle: "bold" },
|
|
5148
|
+
{ token: "wp-readme.fieldValue", foreground: "334155" },
|
|
5149
|
+
{ token: "wp-readme.delimiter", foreground: "64748b" },
|
|
5150
|
+
{ token: "wp-readme.listMarker", foreground: "7c3aed", fontStyle: "bold" },
|
|
5151
|
+
{ token: "wp-readme.inlineCode", foreground: "6d28d9" },
|
|
5152
|
+
{ token: "wp-readme.codeBlock", foreground: "047857" },
|
|
5153
|
+
{ token: "wp-readme.link", foreground: "0969da", fontStyle: "underline" },
|
|
5154
|
+
{ token: "wp-readme.url", foreground: "0284c7", fontStyle: "underline" },
|
|
5155
|
+
{ token: "wp-readme.command", foreground: "92400e" },
|
|
5156
|
+
{ token: "wp-readme.shortcode", foreground: "be185d" },
|
|
5157
|
+
{ token: "wp-readme.quote", foreground: "6b7280", fontStyle: "italic" },
|
|
5158
|
+
{ token: "wp-readme.strong", foreground: "111827", fontStyle: "bold" },
|
|
5159
|
+
{ token: "wp-readme.emphasis", foreground: "374151", fontStyle: "italic" }
|
|
5160
|
+
],
|
|
5161
|
+
colors: {
|
|
5162
|
+
"editor.background": "#ffffff"
|
|
5163
|
+
}
|
|
5164
|
+
});
|
|
5165
|
+
}
|
|
5166
|
+
|
|
4084
5167
|
function languageForPath(filePath) {
|
|
5168
|
+
const fileName = String(filePath ?? "").split("/").pop()?.toLowerCase();
|
|
5169
|
+
if (fileName === "readme.txt") {
|
|
5170
|
+
return "wordpress-readme";
|
|
5171
|
+
}
|
|
4085
5172
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
4086
5173
|
const map = {
|
|
4087
5174
|
css: "css",
|
|
@@ -4634,7 +5721,7 @@ function localCard(plugin, versionState) {
|
|
|
4634
5721
|
Details
|
|
4635
5722
|
</button>
|
|
4636
5723
|
<button type="button" role="menuitem" data-action="manage-release" data-id="${escapeAttr(plugin.id)}">
|
|
4637
|
-
<span class="dashicons
|
|
5724
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
4638
5725
|
Manage release
|
|
4639
5726
|
</button>
|
|
4640
5727
|
<button type="button" role="menuitem" data-action="version-state" data-id="${escapeAttr(plugin.id)}">
|
|
@@ -4670,7 +5757,7 @@ function localCard(plugin, versionState) {
|
|
|
4670
5757
|
Open in Studio
|
|
4671
5758
|
</button>
|
|
4672
5759
|
<button type="button" class="ps-plugin-card-secondary" data-action="manage-release" data-id="${escapeAttr(plugin.id)}" ${missing ? "disabled" : ""}>
|
|
4673
|
-
<span class="dashicons
|
|
5760
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
4674
5761
|
Manage release
|
|
4675
5762
|
</button>
|
|
4676
5763
|
</footer>
|
|
@@ -4683,6 +5770,12 @@ function localCard(plugin, versionState) {
|
|
|
4683
5770
|
* =================================================================== */
|
|
4684
5771
|
|
|
4685
5772
|
async function showDetails(scope, id) {
|
|
5773
|
+
if (state.activeView === "studio") {
|
|
5774
|
+
appendStudioCliCommand(studioCliCommand([
|
|
5775
|
+
"info",
|
|
5776
|
+
scope === "local" ? localPluginCliTarget(id) : quoteCliArg(id)
|
|
5777
|
+
]));
|
|
5778
|
+
}
|
|
4686
5779
|
els.detail.classList.add("is-open");
|
|
4687
5780
|
els.detail.setAttribute("aria-hidden", "false");
|
|
4688
5781
|
els.detail.innerHTML = `
|
|
@@ -4785,6 +5878,7 @@ function closeDetail() {
|
|
|
4785
5878
|
* =================================================================== */
|
|
4786
5879
|
|
|
4787
5880
|
async function createJob(body) {
|
|
5881
|
+
appendStudioCliCommand(studioCliCommandForJob(body));
|
|
4788
5882
|
const job = await api("/api/jobs", { method: "POST", body });
|
|
4789
5883
|
upsertJob(job);
|
|
4790
5884
|
subscribeJob(job.id);
|
|
@@ -5587,7 +6681,7 @@ function commandItems() {
|
|
|
5587
6681
|
id: "view:release",
|
|
5588
6682
|
title: "Go to Release Management",
|
|
5589
6683
|
subtitle: "Release status for every local plugin",
|
|
5590
|
-
icon: "
|
|
6684
|
+
icon: "ps-icon-rocket",
|
|
5591
6685
|
run: () => showView("release")
|
|
5592
6686
|
},
|
|
5593
6687
|
{
|
|
@@ -5647,7 +6741,7 @@ function commandItems() {
|
|
|
5647
6741
|
id: `release:${plugin.id}`,
|
|
5648
6742
|
title: `Manage release • ${plugin.name}`,
|
|
5649
6743
|
subtitle: plugin.slug,
|
|
5650
|
-
icon: "
|
|
6744
|
+
icon: "ps-icon-rocket",
|
|
5651
6745
|
run: () => {
|
|
5652
6746
|
void openStudio("local", plugin.id, { sidebarTab: "release" });
|
|
5653
6747
|
}
|
|
@@ -6203,6 +7297,22 @@ function isSafeMarkdownHref(value) {
|
|
|
6203
7297
|
}
|
|
6204
7298
|
}
|
|
6205
7299
|
|
|
7300
|
+
function formatStudioBytes(bytes) {
|
|
7301
|
+
const value = Number(bytes);
|
|
7302
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
7303
|
+
return "0 B";
|
|
7304
|
+
}
|
|
7305
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
7306
|
+
let size = value;
|
|
7307
|
+
let unitIndex = 0;
|
|
7308
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
7309
|
+
size /= 1024;
|
|
7310
|
+
unitIndex += 1;
|
|
7311
|
+
}
|
|
7312
|
+
const precision = unitIndex === 0 || size >= 10 ? 0 : 1;
|
|
7313
|
+
return `${size.toFixed(precision)} ${units[unitIndex]}`;
|
|
7314
|
+
}
|
|
7315
|
+
|
|
6206
7316
|
function escapeHtml(value) {
|
|
6207
7317
|
return String(value)
|
|
6208
7318
|
.replaceAll("&", "&")
|
|
@@ -6315,7 +7425,7 @@ function renderReleaseBoard() {
|
|
|
6315
7425
|
els.release.innerHTML = emptyState({
|
|
6316
7426
|
title: "No local plugins to release.",
|
|
6317
7427
|
message: "Add a plugin folder to your Local Library, then come back here.",
|
|
6318
|
-
icon: "
|
|
7428
|
+
icon: "ps-icon-rocket"
|
|
6319
7429
|
});
|
|
6320
7430
|
return;
|
|
6321
7431
|
}
|
|
@@ -6324,7 +7434,7 @@ function renderReleaseBoard() {
|
|
|
6324
7434
|
els.release.innerHTML = `
|
|
6325
7435
|
<div class="ps-card-toolbar" role="region" aria-label="Release board summary">
|
|
6326
7436
|
<span class="ps-card-toolbar-count">
|
|
6327
|
-
<span class="dashicons
|
|
7437
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6328
7438
|
${escapeHtml(
|
|
6329
7439
|
`${state.releaseBoard.plugins.length} plugin${state.releaseBoard.plugins.length === 1 ? "" : "s"} tracked`
|
|
6330
7440
|
)}
|
|
@@ -6367,7 +7477,7 @@ function releaseBoardCard(entry) {
|
|
|
6367
7477
|
: ""}
|
|
6368
7478
|
<footer class="ps-release-board-card-footer">
|
|
6369
7479
|
<button type="button" class="button button-primary" data-action="manage-release" data-id="${escapeAttr(entry.id)}" ${entry.exists === false ? "disabled" : ""}>
|
|
6370
|
-
<span class="dashicons
|
|
7480
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6371
7481
|
Manage release
|
|
6372
7482
|
</button>
|
|
6373
7483
|
<button type="button" class="ps-plugin-card-secondary" data-action="version-state" data-id="${escapeAttr(entry.id)}" ${entry.exists === false ? "disabled" : ""}>
|
|
@@ -6398,6 +7508,9 @@ function setStudioSidebarTab(tab) {
|
|
|
6398
7508
|
if (next === "release" && state.studio.scope === "local" && !state.studio.release.tags) {
|
|
6399
7509
|
void loadStudioReleaseTags();
|
|
6400
7510
|
}
|
|
7511
|
+
if (next === "release" && state.studio.scope === "local") {
|
|
7512
|
+
void refreshStudioIgnoreState({ files: true });
|
|
7513
|
+
}
|
|
6401
7514
|
}
|
|
6402
7515
|
|
|
6403
7516
|
function updateStudioSidebar() {
|
|
@@ -6422,7 +7535,7 @@ function renderStudioReleasePane() {
|
|
|
6422
7535
|
return `
|
|
6423
7536
|
<div class="studio-release-pane">
|
|
6424
7537
|
<div class="studio-release-empty">
|
|
6425
|
-
<span class="dashicons
|
|
7538
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6426
7539
|
<strong>Release management is local-only</strong>
|
|
6427
7540
|
<p>Open a local plugin to manage its release lifecycle.</p>
|
|
6428
7541
|
</div>
|
|
@@ -6433,7 +7546,7 @@ function renderStudioReleasePane() {
|
|
|
6433
7546
|
return `
|
|
6434
7547
|
<div class="studio-release-pane">
|
|
6435
7548
|
<div class="studio-release-empty">
|
|
6436
|
-
<span class="dashicons
|
|
7549
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
6437
7550
|
<strong>Open a plugin to start</strong>
|
|
6438
7551
|
</div>
|
|
6439
7552
|
</div>
|
|
@@ -6457,6 +7570,7 @@ function renderStudioReleasePane() {
|
|
|
6457
7570
|
${renderStudioReleaseStepVersion(versionState, release)}
|
|
6458
7571
|
${renderStudioReleaseStepTags(versionState, release)}
|
|
6459
7572
|
${renderStudioReleaseStepValidate(versionState, release)}
|
|
7573
|
+
${renderStudioReleaseStepIgnored()}
|
|
6460
7574
|
${renderStudioReleaseStepPublish(versionState, release)}
|
|
6461
7575
|
</ol>
|
|
6462
7576
|
</div>
|
|
@@ -6479,6 +7593,16 @@ function renderStudioReleaseStepShell(number, title, summary, body) {
|
|
|
6479
7593
|
`;
|
|
6480
7594
|
}
|
|
6481
7595
|
|
|
7596
|
+
function toggleStudioReleaseIgnored() {
|
|
7597
|
+
state.studio.release = {
|
|
7598
|
+
...state.studio.release,
|
|
7599
|
+
ignoredCollapsed: !(state.studio.release?.ignoredCollapsed ?? true)
|
|
7600
|
+
};
|
|
7601
|
+
renderStudio();
|
|
7602
|
+
remountStudioEditorIfNeeded();
|
|
7603
|
+
updateStudioControls();
|
|
7604
|
+
}
|
|
7605
|
+
|
|
6482
7606
|
function renderStudioReleaseStepVersion(versionState, release) {
|
|
6483
7607
|
const localVersion = versionState?.localVersion ?? "—";
|
|
6484
7608
|
const stable = versionState?.readmeStableTag ?? "—";
|
|
@@ -6694,6 +7818,72 @@ function renderStudioReleaseStepValidate(versionState, release) {
|
|
|
6694
7818
|
return renderStudioReleaseStepShell(3, "Validate", summaryLine, body);
|
|
6695
7819
|
}
|
|
6696
7820
|
|
|
7821
|
+
function renderStudioReleaseStepIgnored() {
|
|
7822
|
+
const ignoreState = state.studio.ignoreState ?? createInitialStudioIgnoreState();
|
|
7823
|
+
const patterns = ignoreState.patterns ?? [];
|
|
7824
|
+
const ignoredFiles = ignoreState.ignoredFiles ?? [];
|
|
7825
|
+
const collapsed = state.studio.release?.ignoredCollapsed ?? true;
|
|
7826
|
+
const summary = patterns.length
|
|
7827
|
+
? `${patterns.length} pattern${patterns.length === 1 ? "" : "s"} · ${ignoredFiles.length} file${ignoredFiles.length === 1 ? "" : "s"}`
|
|
7828
|
+
: "No project ignore rules yet.";
|
|
7829
|
+
|
|
7830
|
+
let body;
|
|
7831
|
+
if (state.studio.ignoreLoading) {
|
|
7832
|
+
body = loadingShell("Reading ignored files…");
|
|
7833
|
+
} else if (state.studio.ignoreError) {
|
|
7834
|
+
body = `<p class="ps-release-step-error">${escapeHtml(state.studio.ignoreError)}</p>`;
|
|
7835
|
+
} else if (!patterns.length) {
|
|
7836
|
+
body = `<p class="ps-release-step-muted">No project ignore rules yet.</p>`;
|
|
7837
|
+
} else {
|
|
7838
|
+
body = `
|
|
7839
|
+
<ul class="ps-release-ignore-patterns">
|
|
7840
|
+
${patterns.map((pattern) => renderStudioIgnorePatternRow(pattern)).join("")}
|
|
7841
|
+
</ul>
|
|
7842
|
+
${ignoredFiles.length
|
|
7843
|
+
? `<ul class="ps-release-ignored-files">
|
|
7844
|
+
${ignoredFiles.slice(0, 8).map((file) => `
|
|
7845
|
+
<li>
|
|
7846
|
+
<span class="dashicons ${studioFileIcon(file.path)}" aria-hidden="true"></span>
|
|
7847
|
+
<span title="${escapeAttr(file.path)}">${escapeHtml(file.path)}</span>
|
|
7848
|
+
<small>${escapeHtml(file.ignoredBy ?? "")}</small>
|
|
7849
|
+
</li>
|
|
7850
|
+
`).join("")}
|
|
7851
|
+
</ul>
|
|
7852
|
+
${ignoredFiles.length > 8 ? `<p class="ps-release-step-muted">${escapeHtml(`${ignoredFiles.length - 8} more ignored file${ignoredFiles.length - 8 === 1 ? "" : "s"}.`)}</p>` : ""}`
|
|
7853
|
+
: `<p class="ps-release-step-muted">The patterns do not match any visible project files.</p>`}
|
|
7854
|
+
`;
|
|
7855
|
+
}
|
|
7856
|
+
|
|
7857
|
+
return `
|
|
7858
|
+
<li class="ps-release-step ps-release-step-collapsible${collapsed ? " is-collapsed" : ""}">
|
|
7859
|
+
<span class="ps-release-step-connector" aria-hidden="true"></span>
|
|
7860
|
+
<header class="ps-release-step-header">
|
|
7861
|
+
<button class="ps-release-step-toggle" type="button" data-action="studio-release-toggle-ignored" aria-expanded="${collapsed ? "false" : "true"}">
|
|
7862
|
+
<span class="ps-release-step-marker">4</span>
|
|
7863
|
+
<span class="ps-release-step-heading">
|
|
7864
|
+
<strong>Ignored files</strong>
|
|
7865
|
+
<small>${escapeHtml(summary)}</small>
|
|
7866
|
+
</span>
|
|
7867
|
+
<span class="dashicons ${collapsed ? "dashicons-arrow-right-alt2" : "dashicons-arrow-down-alt2"} ps-release-step-toggle-icon" aria-hidden="true"></span>
|
|
7868
|
+
</button>
|
|
7869
|
+
</header>
|
|
7870
|
+
${collapsed ? "" : `<div class="ps-release-step-body">${body}</div>`}
|
|
7871
|
+
</li>
|
|
7872
|
+
`;
|
|
7873
|
+
}
|
|
7874
|
+
|
|
7875
|
+
function renderStudioIgnorePatternRow(pattern) {
|
|
7876
|
+
const busy = state.studio.ignoreBusyPattern === pattern;
|
|
7877
|
+
return `
|
|
7878
|
+
<li>
|
|
7879
|
+
<code>${escapeHtml(pattern)}</code>
|
|
7880
|
+
<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\"" : ""}>
|
|
7881
|
+
<span class="dashicons ${busy ? "dashicons-update" : "dashicons-no-alt"}" aria-hidden="true"></span>
|
|
7882
|
+
</button>
|
|
7883
|
+
</li>
|
|
7884
|
+
`;
|
|
7885
|
+
}
|
|
7886
|
+
|
|
6697
7887
|
function studioPublishRouteDetails(action) {
|
|
6698
7888
|
switch (action) {
|
|
6699
7889
|
case "submit":
|
|
@@ -6710,7 +7900,7 @@ function studioPublishRouteDetails(action) {
|
|
|
6710
7900
|
return {
|
|
6711
7901
|
label: "Release update",
|
|
6712
7902
|
shortLabel: "release",
|
|
6713
|
-
icon: "
|
|
7903
|
+
icon: "ps-icon-rocket",
|
|
6714
7904
|
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
7905
|
dryRunLabel: "Dry-run release",
|
|
6716
7906
|
confirmLabel: "Confirm release",
|
|
@@ -6833,7 +8023,7 @@ function renderStudioReleaseStepPublish(versionState, release) {
|
|
|
6833
8023
|
? "Blocked — fix version state before publishing"
|
|
6834
8024
|
: "";
|
|
6835
8025
|
|
|
6836
|
-
return renderStudioReleaseStepShell(
|
|
8026
|
+
return renderStudioReleaseStepShell(5, "Submit / Release", summary, body);
|
|
6837
8027
|
}
|
|
6838
8028
|
|
|
6839
8029
|
/* ===================================================================
|
|
@@ -6870,16 +8060,19 @@ async function refreshStudioAfterReleaseSwitch(result = {}) {
|
|
|
6870
8060
|
const localId = state.studio.id;
|
|
6871
8061
|
const selectedPath = state.studio.selectedFile?.path;
|
|
6872
8062
|
try {
|
|
6873
|
-
const [detail, filesResult, checkState, versionState] = await Promise.all([
|
|
8063
|
+
const [detail, filesResult, checkState, versionState, ignoreState] = await Promise.all([
|
|
6874
8064
|
api(`/api/plugins/local/${encodeURIComponent(localId)}`),
|
|
6875
8065
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/files`),
|
|
6876
8066
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/check-state`).catch(() => ({ state: null })),
|
|
6877
|
-
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null)
|
|
8067
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null),
|
|
8068
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/ignore-state`).catch(() => createInitialStudioIgnoreState())
|
|
6878
8069
|
]);
|
|
6879
8070
|
|
|
6880
8071
|
applyStudioPluginDetail("local", localId, detail);
|
|
6881
8072
|
state.studio.files = filesResult.files ?? [];
|
|
8073
|
+
state.studio.directories = filesResult.directories ?? [];
|
|
6882
8074
|
applyStudioCheckState(checkState.state);
|
|
8075
|
+
applyStudioIgnoreState(ignoreState);
|
|
6883
8076
|
if (versionState) {
|
|
6884
8077
|
state.versionStates.set(localId, versionState);
|
|
6885
8078
|
renderLocal();
|
|
@@ -6917,16 +8110,19 @@ async function refreshStudioAfterVersionChange(localId) {
|
|
|
6917
8110
|
|
|
6918
8111
|
const selectedPath = state.studio.selectedFile?.path;
|
|
6919
8112
|
try {
|
|
6920
|
-
const [detail, filesResult, checkState, versionState] = await Promise.all([
|
|
8113
|
+
const [detail, filesResult, checkState, versionState, ignoreState] = await Promise.all([
|
|
6921
8114
|
api(`/api/plugins/local/${encodeURIComponent(localId)}`),
|
|
6922
8115
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/files`),
|
|
6923
8116
|
api(`/api/plugins/local/${encodeURIComponent(localId)}/check-state`).catch(() => ({ state: null })),
|
|
6924
|
-
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null)
|
|
8117
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/version-state`).catch(() => null),
|
|
8118
|
+
api(`/api/plugins/local/${encodeURIComponent(localId)}/ignore-state`).catch(() => createInitialStudioIgnoreState())
|
|
6925
8119
|
]);
|
|
6926
8120
|
|
|
6927
8121
|
applyStudioPluginDetail("local", localId, detail);
|
|
6928
8122
|
state.studio.files = filesResult.files ?? [];
|
|
8123
|
+
state.studio.directories = filesResult.directories ?? [];
|
|
6929
8124
|
applyStudioCheckState(checkState.state);
|
|
8125
|
+
applyStudioIgnoreState(ignoreState);
|
|
6930
8126
|
if (versionState) {
|
|
6931
8127
|
state.versionStates.set(localId, versionState);
|
|
6932
8128
|
if (!state.studio.release.newTagDraft || state.studio.release.newTagDraft === versionState.latestSvnTag) {
|
|
@@ -7025,6 +8221,7 @@ async function switchStudioReleaseTag(tag, resolution) {
|
|
|
7025
8221
|
|
|
7026
8222
|
async function bumpStudioReleaseVersion(localId, bump) {
|
|
7027
8223
|
if (!localId || !["patch", "minor", "major"].includes(bump)) return;
|
|
8224
|
+
appendStudioCliCommand(studioCliCommand(["version", bump, localPluginCliTarget(localId)]));
|
|
7028
8225
|
if (state.studio.id !== localId) {
|
|
7029
8226
|
// Outside the funnel (e.g. invoked from a command palette). Run without
|
|
7030
8227
|
// the inline busy state — the global notice is enough feedback.
|