diff --git a/src/features/plugins/PluginsPage.module.css b/src/features/plugins/PluginsPage.module.css
index a2764a19b..90b2c7398 100644
--- a/src/features/plugins/PluginsPage.module.css
+++ b/src/features/plugins/PluginsPage.module.css
@@ -369,17 +369,6 @@
padding: 4px 8px;
}
-.marketBar {
- display: flex;
- gap: 4px;
- align-items: center;
-}
-
-.marketBar > :first-child {
- flex: 1;
- min-width: 0;
-}
-
.categorySelect {
width: 100%;
}
diff --git a/src/features/plugins/PluginsPage.tsx b/src/features/plugins/PluginsPage.tsx
index f2dac512a..7c66e6c15 100644
--- a/src/features/plugins/PluginsPage.tsx
+++ b/src/features/plugins/PluginsPage.tsx
@@ -16,7 +16,6 @@ import {
EmptyState,
Icon,
IconButton,
- Input,
SearchField,
Select,
Spinner,
@@ -48,6 +47,7 @@ import {
} from "./store";
import { useProject } from "@/platform/project";
import { importIntellijTheme, showInstalledIntellijThemes } from "./intellijImport";
+import { promptInput } from "@/workbench/inputDialogs";
import styles from "./PluginsPage.module.css";
import { tx } from "@/platform/i18n";
@@ -537,6 +537,21 @@ interface GearActions {
install: (packageFile: boolean) => void;
importIntellijFile: () => void;
importInstalledIntellij: () => void;
+ editRepository: () => void;
+}
+
+/** Asks for the marketplace index URL; an empty one restores the default. */
+async function editMarketUrl() {
+ const url = await promptInput({
+ title: "Plugin Repository",
+ label: "Marketplace index URL:",
+ initial: usePlugins.getState().marketUrl ?? "",
+ placeholder: "https://…/index.json or a folder path",
+ okLabel: "Save",
+ validate: () => null,
+ hint: Leave empty to use the default repository.,
+ });
+ if (url !== null) await setMarketUrl(url);
}
function gearMenu(plugins: readonly PluginInfo[], updates: number, actions: GearActions): MenuItem[] {
@@ -550,6 +565,7 @@ function gearMenu(plugins: readonly PluginInfo[], updates: number, actions: Gear
{ kind: "separator" },
{ label: "Install Plugin from Disk…", icon: "package", run: () => actions.install(false) },
{ label: "Install Plugin from Package File…", run: () => actions.install(true) },
+ { label: "Plugin Repository…", run: actions.editRepository },
...(actions.canImport
? [
{ kind: "separator" as const },
@@ -654,30 +670,6 @@ function CategoryFilter({
);
}
-/** The marketplace index URL, editable in place. */
-function MarketUrlBar() {
- const current = usePlugins((s) => s.marketUrl);
- const [value, setValue] = useState(current ?? "");
- useEffect(() => setValue(current ?? ""), [current]);
- return (
-
- );
-}
-
function MarketRow({ entry, selected, onSelect }: { entry: MarketPlugin; selected: boolean; onSelect: () => void }) {
const update = isUpdate(entry);
return (
@@ -832,6 +824,7 @@ export function PluginsPage() {
install: (packageFile) => void installFromDisk(undefined, packageFile).then(showInstalled),
importIntellijFile: () => void importIntellijTheme().then(showInstalled),
importInstalledIntellij: () => showInstalledIntellijThemes(showInstalled),
+ editRepository: () => void editMarketUrl(),
}),
);
};
@@ -876,7 +869,7 @@ export function PluginsPage() {
className={styles.search}
value={query}
onChange={setQuery}
- placeholder={tab === "installed" ? "Search installed plugins" : "Search local plugins"}
+ placeholder={tab === "installed" ? "Search installed plugins" : "Search plugins"}
aria-label="Filter plugins"
/>
{tab === "installed" ? (
@@ -916,20 +909,11 @@ export function PluginsPage() {
) : (
- -
-
-
{(market?.length ?? 0) > 0 && (
-
)}
- {!marketUrl && !marketError && (
- -
-
- {tx`${NO_REPOSITORY}. Showing plugin packages available on this machine.`}
-
- )}
{marketError && - {marketError}
}
{marketShown.length > 0 && }
{marketShown.map((m) => (
@@ -983,6 +967,8 @@ export function PluginsPage() {
/>
) : selectedEntry ? (
+ ) : marketUrl ? (
+
) : (
Install a plugin folder with plugin.toml{" "}
diff --git a/src/features/plugins/__tests__/plugins.test.tsx b/src/features/plugins/__tests__/plugins.test.tsx
index b97478793..72b65f4b9 100644
--- a/src/features/plugins/__tests__/plugins.test.tsx
+++ b/src/features/plugins/__tests__/plugins.test.tsx
@@ -9,6 +9,7 @@ import { executeAction, executeActionFrom, getAction, registerAction } from "@/p
import { useProject } from "@/platform/project";
import { pluginThemeRef, useContributedIconThemes } from "@/platform/themes";
import { ContextMenuHost, hideContextMenu } from "@/ui";
+import { InputDialogsHost } from "@/workbench/inputDialogs";
import { provideNotificationService, type NotificationService } from "@/workbench/contributions";
import { applyThemeAttributes, previewTheme, useThemeOverride } from "@/workbench/theme";
import { createMemoryPluginsBackend, sampleCatalog, setPluginsBackend, type PluginsBackend } from "../backend";
@@ -561,7 +562,6 @@ describe("PluginsPage", () => {
render();
await user.click(screen.getByRole("tab", { name: /Available/ }));
const list = await screen.findByRole("list", { name: "Available plugins" });
- expect(within(list).getByText(new RegExp(NO_REPOSITORY))).toBeInTheDocument();
await waitFor(() => expect(within(list).getByText("Toy Language")).toBeInTheDocument());
await user.click(within(list).getByText("Toy Language"));
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent("Toy Language");
@@ -663,6 +663,53 @@ describe("marketplace", () => {
expect(asked[0]).toMatchObject({ pluginId: "hello", kind: "install" });
});
+ it("doesn't claim there is no repository while one is configured", async () => {
+ setPluginsBackend({
+ ...createMemoryPluginsBackend([], []),
+ marketUrl: async () => "https://plugins.example/index.json",
+ market: async () => [],
+ });
+ const user = userEvent.setup();
+ render();
+ await user.click(screen.getByRole("tab", { name: /Available/ }));
+ expect(await screen.findByText("No plugin selected")).toBeInTheDocument();
+ expect(screen.queryByText(new RegExp(NO_REPOSITORY))).not.toBeInTheDocument();
+ expect(screen.getByRole("textbox", { name: "Filter plugins" })).toHaveAttribute("placeholder", "Search plugins");
+ });
+
+ it("edits the repository from the gear menu; an empty URL restores the default", async () => {
+ const base = createMemoryPluginsBackend([], []);
+ const set: (string | null)[] = [];
+ setPluginsBackend({
+ ...base,
+ marketUrl: async () => "https://plugins.example/index.json",
+ market: async () => [],
+ setMarketUrl: async (url) => void set.push(url),
+ });
+ const user = userEvent.setup();
+ render(
+ <>
+
+
+
+ >,
+ );
+ await user.click(screen.getByRole("tab", { name: /Available/ }));
+ expect(screen.queryByRole("textbox", { name: /Marketplace index URL/ })).not.toBeInTheDocument();
+ const edit = async (value: string) => {
+ await user.click(screen.getByRole("button", { name: "Manage Plugins" }));
+ await user.click(screen.getByRole("menuitem", { name: "Plugin Repository…" }));
+ const field = await screen.findByRole("textbox", { name: /Marketplace index URL/ });
+ await waitFor(() => expect(field).toHaveValue("https://plugins.example/index.json"));
+ await user.clear(field);
+ if (value) await user.type(field, value);
+ await user.click(screen.getByRole("button", { name: "Save" }));
+ };
+ await edit("https://mirror.example/index.json");
+ await edit("");
+ await waitFor(() => expect(set).toEqual(["https://mirror.example/index.json", null]));
+ });
+
it("filters the marketplace by category", async () => {
const entry = (id: string, name: string, categories: string[]) => ({
id,
diff --git a/src/platform/locales/text/fr.json b/src/platform/locales/text/fr.json
index dd7aea5f8..c015e9f07 100644
--- a/src/platform/locales/text/fr.json
+++ b/src/platform/locales/text/fr.json
@@ -1859,7 +1859,6 @@
"Search frames": "Rechercher des cadres",
"Search in comments and strings": "Rechercher dans les commentaires et les chaînes",
"Search installed plugins": "Rechercher parmi les plugins installés",
- "Search local plugins": "Rechercher parmi les plugins locaux",
"Search packages": "Rechercher des paquets",
"Search projects": "Rechercher des projets",
"Search pull requests": "Rechercher des pull requests",
@@ -4406,7 +4405,6 @@
"Memory & Diagnostics": "Mémoire et diagnostics",
"Over budget ({budget})": "Budget dépassé ({budget})",
"dev": "dev",
- "{label}. Showing plugin packages available on this machine.": "{label}. Affichage des packages de plugins disponibles sur cette machine.",
"Zoomed into {name}": "Zoom sur {name}",
"Rename directory '{current}' and its usages to:": "Renommer le répertoire '{current}' et ses utilisations en :",
"Rename file '{current}' and its usages to:": "Renommer le fichier '{current}' et ses utilisations en :",
@@ -5548,8 +5546,6 @@
"Unsigned": "Non signé",
"Roll Back to {previousVersion}": "Revenir à {previousVersion}",
"Install Plugin from Package File…": "Installer un plugin depuis un fichier de paquet…",
- "Marketplace index URL": "URL de l'index de la marketplace",
- "Refresh Marketplace": "Actualiser la marketplace",
"The plugin system is a preview: its API and the plugins may still change.": "Le système de plugins est en préversion : son API et les plugins peuvent encore changer.",
"Linked for development": "Lié pour le développement",
"Marketplace": "Marketplace",
@@ -5782,7 +5778,6 @@
"Weekly limit": "Limite hebdomadaire",
"Weekly Opus limit": "Limite hebdomadaire Opus",
"Weekly Sonnet limit": "Limite hebdomadaire Sonnet",
- "Marketplace index URL (https://…/index.json or a folder path)": "URL de l'index de la marketplace (https://…/index.json ou un chemin de dossier)",
"Editor Providers": "Fournisseurs d'éditeurs",
"Syntax errors": "Erreurs de syntaxe",
"Pull Members Up…": "Remonter les membres…",
diff --git a/src/platform/locales/text/ko.json b/src/platform/locales/text/ko.json
index caa87c948..269aa70d3 100644
--- a/src/platform/locales/text/ko.json
+++ b/src/platform/locales/text/ko.json
@@ -1937,7 +1937,6 @@
"Search frames": "프레임 검색",
"Search in comments and strings": "주석 및 문자열에서 검색",
"Search installed plugins": "설치된 플러그인 검색",
- "Search local plugins": "로컬 플러그인 검색",
"Search packages": "패키지 검색",
"Search projects": "프로젝트 검색",
"Search pull requests": "풀 리퀘스트 검색",
@@ -4517,7 +4516,6 @@
"Memory & Diagnostics": "메모리 및 진단",
"Over budget ({budget})": "예산 초과({budget})",
"dev": "개발",
- "{label}. Showing plugin packages available on this machine.": "{label}. 이 머신에서 사용 가능한 플러그인 패키지를 표시합니다.",
"Zoomed into {name}": "{name} 확대됨",
"Rename directory '{current}' and its usages to:": "디렉터리 '{current}' 및 해당 사용 위치의 새 이름:",
"Rename file '{current}' and its usages to:": "파일 '{current}' 및 해당 사용 위치의 새 이름:",
@@ -5708,14 +5706,11 @@
"Unsigned": "서명되지 않음",
"Roll Back to {previousVersion}": "{previousVersion}(으)로 롤백",
"Install Plugin from Package File…": "패키지 파일에서 플러그인 설치…",
- "Marketplace index URL": "마켓플레이스 인덱스 URL",
- "Refresh Marketplace": "마켓플레이스 새로 고침",
"Marketplace": "마켓플레이스",
"Could not download plugin": "플러그인을 다운로드할 수 없습니다",
"Rolled back {name} to {version}": "{name}을(를) {version}(으)로 롤백했습니다",
"Could not roll back": "롤백할 수 없습니다",
"Could not open the package": "패키지를 열 수 없습니다",
- "Marketplace index URL (https://…/index.json or a folder path)": "마켓플레이스 인덱스 URL(https://…/index.json 또는 폴더 경로)",
"Settings of the plugin; projects can override them.": "플러그인 설정입니다. 프로젝트에서 재정의할 수 있습니다.",
"Menus": "메뉴",
"Shortcuts": "단축키",
diff --git a/src/platform/locales/text/zh.json b/src/platform/locales/text/zh.json
index f38f73f68..78259d0a5 100644
--- a/src/platform/locales/text/zh.json
+++ b/src/platform/locales/text/zh.json
@@ -1940,8 +1940,8 @@
"Search frames": "搜索帧",
"Search in comments and strings": "在注释和字符串中搜索",
"Search installed plugins": "搜索已安装的插件",
- "Search local plugins": "搜索本地插件",
"Search packages": "搜索软件包",
+ "Search plugins": "搜索插件",
"Search projects": "搜索项目",
"Search pull requests": "搜索拉取请求",
"Search query": "搜索查询",
@@ -4535,7 +4535,6 @@
"Memory & Diagnostics": "内存与诊断",
"Over budget ({budget})": "超出预算({budget})",
"dev": "开发",
- "{label}. Showing plugin packages available on this machine.": "{label}。显示此计算机上可用的插件包。",
"Zoomed into {name}": "已缩放到 {name}",
"Rename directory '{current}' and its usages to:": "将目录“{current}”及其用法重命名为:",
"Rename file '{current}' and its usages to:": "将文件“{current}”及其用法重命名为:",
@@ -5791,14 +5790,15 @@
"1 theme": "1 个主题",
"{count} themes": "{count} 个主题",
"No theme plugins found in JetBrains IDEs on this computer. Choose a theme plugin downloaded from the JetBrains Marketplace (.jar or .zip) or a .theme.json file instead.": "在此计算机上的 JetBrains IDE 中未找到主题插件。请改为选择从 JetBrains Marketplace 下载的主题插件(.jar 或 .zip)或 .theme.json 文件。",
- "Marketplace index URL": "插件市场索引 URL",
- "Refresh Marketplace": "刷新插件市场",
"Marketplace": "插件市场",
+ "Plugin Repository…": "插件仓库…",
+ "Plugin Repository": "插件仓库",
+ "Marketplace index URL:": "插件市场索引 URL:",
+ "Leave empty to use the default repository.": "留空则使用默认仓库。",
"Could not download plugin": "无法下载插件",
"Rolled back {name} to {version}": "已将 {name} 回滚到 {version}",
"Could not roll back": "无法回滚",
"Could not open the package": "无法打开插件包",
- "Marketplace index URL (https://…/index.json or a folder path)": "插件市场索引 URL(https://…/index.json 或文件夹路径)",
"Settings of the plugin; projects can override them.": "插件的设置;项目可以覆盖。",
"Menus": "菜单",
"Shortcuts": "快捷键",