Files
openidea/src/features/web/index.ts
T
Jaw 55836e1b49 chore: Format the codebase with oxfmt and check formatting in CI (#135)
Run oxfmt once over the whole repository (formatting only, no logic changes) and add `pnpm format:check` to the frontend CI job so unformatted code no longer builds up. `.oxfmtrc.json` now skips the generated theme docs, test fixtures and code blocks inside Markdown.

Co-authored-by: jaw52 <[email protected]>
2026-10-28 19:35:16 +08:00

127 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Web browsers (IDEA's Open In › Browser and the editor's browser bar): HTML
* and SVG files open in an installed browser through the built-in server, and
* HTML files get a built-in preview beside the editor. ⌃⇧R in an HTML editor
* opens the default browser, like IDEA's Run for HTML files.
*/
import { registerActions } from "@/platform/actions";
import { getContext } from "@/platform/context";
import { basename } from "@/platform/project";
import { lazyComponent, type MenuItem } from "@/ui";
import { maybeEditorService, registerMenuGroup, registerTextEditorWrapper } from "@/workbench/contributions";
import {
browserIcon,
isHtmlPath,
isWebPath,
loadBrowsers,
openInBrowser,
togglePreview,
useBrowsers,
useWebPreview,
} from "./browsers";
import { tx } from "@/platform/i18n";
interface OpenArg {
path?: string;
browser?: string;
}
function targetPath(arg: unknown): string | null {
if (typeof arg === "string") return arg;
const path = (arg as OpenArg | undefined)?.path ?? getContext().editorPath;
return typeof path === "string" ? path : null;
}
async function showPreview(path: string) {
await maybeEditorService()?.openFile(path, { focus: true });
togglePreview(path, true);
}
function browserMenu(path: string): MenuItem {
const html = isHtmlPath(path);
return {
label: "Browser",
icon: "web",
submenu: [
{ kind: "action", action: "web.openInBrowser", arg: { path }, label: "Default Browser" },
{ kind: "separator" },
...(html ? [{ label: "Built-in Preview", icon: "eye", run: () => void showPreview(path) }] : []),
...useBrowsers.getState().list.map((b): MenuItem => ({
label: b.name,
verbatim: true,
icon: browserIcon(b.id),
run: () => void openInBrowser(path, b.id),
})),
],
};
}
export function activate(): () => void {
void loadBrowsers();
const disposers = [
registerTextEditorWrapper({
id: "web.html",
matches: isHtmlPath,
component: lazyComponent(() => import("./WebEditor").then((m) => m.WebEditor)),
}),
registerActions([
{
id: "web.openInBrowser",
title: "Open in Browser",
description: "Open the HTML or SVG file in the default browser through the built-in server",
category: "View",
icon: "web",
when: "projectOpen",
keyWhen: "textEditorActive && editorLangId == html",
keywords: ["browser", "html", "preview", "chrome", "firefox", "safari", "run"],
run: ({ arg }) => {
const path = targetPath(arg);
if (path && isWebPath(path)) return openInBrowser(path, (arg as OpenArg | undefined)?.browser ?? null);
},
},
{
id: "web.togglePreview",
title: "Built-in Preview",
description: "Show the HTML file rendered beside the editor",
category: "View",
icon: "eye",
when: "textEditorActive && editorLangId == html",
keywords: ["browser", "html", "preview", "live"],
toggled: () => {
const path = getContext().editorPath;
return typeof path === "string" && !!useWebPreview.getState()[path];
},
run: ({ arg }) => {
const path = targetPath(arg);
if (path && isHtmlPath(path)) togglePreview(path);
},
},
]),
registerMenuGroup({
id: "web.open",
menus: ["editor", "editorTab", "projectView"],
order: 5,
items: (_menu, { paths, dirs }) => {
const path = paths.length === 1 && !dirs?.[0] ? paths[0]! : null;
if (!path || !isHtmlPath(path)) return [];
return [
{
kind: "action",
action: "web.openInBrowser",
arg: { path },
label: tx`Open '${basename(path)}' in Browser`,
icon: "web",
},
];
},
}),
registerMenuGroup({
id: "web.openIn",
menus: ["openIn"],
items: (_menu, { paths, dirs }) =>
paths.length === 1 && !dirs?.[0] && isWebPath(paths[0]!) ? [browserMenu(paths[0]!)] : [],
}),
];
return () => disposers.reverse().forEach((d) => d());
}