Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion apps/app/src/components/plugin/ExperimentalFileLinkMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import {
getExperimentalFileLocationStart,
} from "@/lib/live-file-navigation";
import { usePluginSlots } from "@/lib/plugin-slots";
import {
formatFileOpenerLabel,
usePluginDisplayNames,
} from "@/lib/plugin-logos";

function getFileExtension(path: string): string | null {
const name = getFileBasename(path);
Expand All @@ -38,6 +42,7 @@ export function ExperimentalFileLinkMenu({
: {}),
});
const { fileOpeners } = usePluginSlots();
const getPluginName = usePluginDisplayNames();
const extension = getFileExtension(intent.target.path);
const matchingOpeners =
extension === null
Expand Down Expand Up @@ -74,7 +79,10 @@ export function ExperimentalFileLinkMenu({
})
}
>
{opener.title}
{formatFileOpenerLabel(
getPluginName(opener.pluginId),
opener.title,
)}
</ContextMenuItem>
))}
</ContextMenuSubContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,34 @@ import {
import { BUILT_IN_FILE_OPENER_PREFERENCE } from "@/lib/file-opener-preference";
import { FileOpenersSettingsSection } from "./FileOpenersSettingsSection";
import { makePluginRegistrationSet } from "@/test/fixtures/plugins";
import {
resetPluginLogoStoreForTest,
setPluginLogoUrls,
} from "@/lib/plugin-logos";

function registerNotesOpener() {
setPluginLogoUrls(
new Map([
[
"simple-notes",
{
displayName: "Docs",
icon: null,
compactIconUrl: null,
logoUrl: null,
logoDarkUrl: null,
icons: new Map(),
},
],
]),
);
setPluginSlotRegistrations(
"notes",
"simple-notes",
makePluginRegistrationSet({
fileOpeners: [
{
id: "editor",
title: "Notes editor",
id: "docs",
title: "Markdown",
extensions: ["md", "mdx"],
component: () => null,
},
Expand All @@ -30,6 +49,7 @@ afterEach(() => {
cleanup();
window.localStorage.clear();
resetPluginSlotStoreForTest();
resetPluginLogoStoreForTest();
});

describe("FileOpenersSettingsSection", () => {
Expand All @@ -42,19 +62,63 @@ describe("FileOpenersSettingsSection", () => {
const trigger = screen.getByRole("button", {
name: "Default opener for .md files",
});
expect(trigger.textContent).toContain("Automatic");
expect(trigger.textContent).toContain("Automatic (Docs)");

await selectOption(trigger, /Built-in preview/u);
expect(storedPreference()).toEqual({
md: BUILT_IN_FILE_OPENER_PREFERENCE,
});

await selectOption(trigger, /Notes editor \(notes\)/u);
expect(storedPreference()).toEqual({ md: "notes:editor" });
await selectOption(trigger, /Docs \(Markdown\)/u);
expect(storedPreference()).toEqual({ md: "simple-notes:docs" });

await selectOption(trigger, /Automatic/u);
expect(storedPreference()).toEqual({});
});

it("shows the plugin name once when the opener title repeats it", async () => {
setPluginLogoUrls(
new Map([
[
"monaco-editor",
{
displayName: "File Editor",
icon: null,
compactIconUrl: null,
logoUrl: null,
logoDarkUrl: null,
icons: new Map(),
},
],
]),
);
setPluginSlotRegistrations(
"monaco-editor",
makePluginRegistrationSet({
fileOpeners: [
{
id: "editor",
title: "File Editor",
extensions: ["ts"],
component: () => null,
},
],
}),
);
render(<FileOpenersSettingsSection />);

const trigger = screen.getByRole("button", {
name: "Default opener for .ts files",
});
fireEvent.pointerDown(trigger, { button: 0 });
const items = await screen.findAllByRole("menuitem");
expect(items.map((item) => item.textContent)).toContain("File Editor");
expect(
items.some((item) =>
item.textContent?.includes("File Editor (File Editor)"),
),
).toBe(false);
});
});

async function selectOption(trigger: HTMLElement, name: RegExp) {
Expand Down
12 changes: 10 additions & 2 deletions apps/app/src/components/settings/FileOpenersSettingsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import {
useFileOpenerPreference,
} from "@/lib/file-opener-preference";
import { usePluginSlots, type PluginFileOpenerSlot } from "@/lib/plugin-slots";
import {
formatFileOpenerLabel,
usePluginDisplayNames,
} from "@/lib/plugin-logos";
import { cn } from "@bb/shared-ui/lib/utils";
import {
SETTINGS_DROPDOWN_CONTENT_CLASS,
Expand Down Expand Up @@ -84,18 +88,22 @@ function ExtensionOpenerControl({
openers: PluginFileOpenerSlot[];
preference: string;
}) {
const getPluginName = usePluginDisplayNames();
const automaticOpener = openers[0];
if (automaticOpener === undefined) return null;

const options = [
{
key: AUTOMATIC_FILE_OPENER_PREFERENCE,
label: `Automatic (${automaticOpener.title})`,
label: `Automatic (${getPluginName(automaticOpener.pluginId)})`,
},
{ key: BUILT_IN_FILE_OPENER_PREFERENCE, label: BUILTIN_LABEL },
...openers.map((opener) => ({
key: buildFileOpenerRef(opener),
label: `${opener.title} (${opener.pluginId})`,
label: formatFileOpenerLabel(
getPluginName(opener.pluginId),
opener.title,
),
})),
];
const selected =
Expand Down
18 changes: 17 additions & 1 deletion apps/app/src/lib/plugin-logos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,28 @@ export function usePluginCompactBranding(
}

export function usePluginDisplayName(pluginId: string): string {
return usePluginDisplayNames()(pluginId);
}

export function usePluginDisplayNames(): (pluginId: string) => string {
const entries = useSyncExternalStore(
subscribePluginLogos,
getPluginLogoUrls,
getPluginLogoUrls,
);
return entries.get(pluginId)?.displayName ?? pluginId;
return useCallback(
(pluginId: string) => entries.get(pluginId)?.displayName ?? pluginId,
[entries],
);
}

export function formatFileOpenerLabel(
pluginName: string,
openerTitle: string,
): string {
return pluginName.trim().toLowerCase() === openerTitle.trim().toLowerCase()
? pluginName
: `${pluginName} (${openerTitle})`;
}

export function usePluginIconUrl(
Expand Down
8 changes: 7 additions & 1 deletion apps/app/src/views/thread-detail/ThreadDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ import {
type OpenAttachmentRequest,
} from "@/components/secondary-panel/AttachmentOpenerContext";
import { usePluginSlots } from "@/lib/plugin-slots";
import {
formatFileOpenerLabel,
usePluginDisplayNames,
} from "@/lib/plugin-logos";
import { getFileExtension } from "@/lib/plugin-slot-resolvers";
import { Icon } from "@bb/shared-ui/icon";
import { getBbDesktopInfo, isDesktopBrowserAvailable } from "@/lib/bb-desktop";
Expand Down Expand Up @@ -688,6 +692,7 @@ function ThreadDetailViewInternal(
fileOpeners: pluginFileOpeners,
threadPanelActions: pluginThreadPanelActions,
} = usePluginSlots();
const getPluginName = usePluginDisplayNames();
useThreadOpenFileSignal({
threadId,
environmentId: thread?.environmentId,
Expand Down Expand Up @@ -2134,7 +2139,7 @@ function ThreadDetailViewInternal(
},
...matching.map((opener) => ({
id: `${opener.pluginId}:${opener.id}`,
label: `Open with ${opener.title}`,
label: `Open with ${formatFileOpenerLabel(getPluginName(opener.pluginId), opener.title)}`,
onSelect: () => {
handleOpenTimelineLocalFileLink(link, {
viewer: { pluginId: opener.pluginId, openerId: opener.id },
Expand Down Expand Up @@ -2175,6 +2180,7 @@ function ThreadDetailViewInternal(
handleOpenTimelineLocalFileLink,
openPathInFileTarget,
pluginFileOpeners,
getPluginName,
],
);
const handleOpenFilePreview = useCallback<OpenFilePreviewHandler>(
Expand Down
Loading