Merge pull request #6847 from ouchihao/codex/issue-4064-custom-page-button-visibility

feat(settings): allow hiding the custom page open button
This commit is contained in:
Wesley Liddick
2026-09-11 09:34:11 +08:00
committed by GitHub
8 changed files with 56 additions and 7 deletions
+8 -7
View File
@@ -9,13 +9,14 @@ import (
// CustomMenuItem represents a user-configured custom menu entry.
type CustomMenuItem struct {
ID string `json:"id"`
Label string `json:"label"`
IconSVG string `json:"icon_svg"`
URL string `json:"url"`
PageSlug string `json:"page_slug,omitempty"`
Visibility string `json:"visibility"` // "user" or "admin"
SortOrder int `json:"sort_order"`
ID string `json:"id"`
Label string `json:"label"`
IconSVG string `json:"icon_svg"`
URL string `json:"url"`
PageSlug string `json:"page_slug,omitempty"`
Visibility string `json:"visibility"` // "user" or "admin"
SortOrder int `json:"sort_order"`
HideOpenButton bool `json:"hide_open_button,omitempty"`
}
// CustomEndpoint represents an admin-configured API endpoint for quick copy.
@@ -677,6 +677,7 @@ export default {
namePlaceholder: 'e.g. Help Center',
url: 'Page URL',
urlPlaceholder: 'https://example.com/page',
hideOpenButton: 'Hide the “Open in new tab” button',
iconSvg: 'SVG Icon',
iconSvgPlaceholder: '<svg>...</svg>',
iconPreview: 'Icon Preview',
@@ -672,6 +672,7 @@ export default {
namePlaceholder: '如:帮助中心',
url: '页面 URL',
urlPlaceholder: 'https://example.com/page',
hideOpenButton: '隐藏“新窗口打开”按钮',
iconSvg: 'SVG 图标',
iconSvgPlaceholder: '<svg>...</svg>',
iconPreview: '图标预览',
+1
View File
@@ -191,6 +191,7 @@ export interface CustomMenuItem {
icon_svg: string
url: string
page_slug?: string
hide_open_button?: boolean
visibility: 'user' | 'admin'
sort_order: number
}
+12
View File
@@ -6763,6 +6763,17 @@
/>
</div>
<label class="flex items-center gap-2 sm:col-span-2">
<input
v-model="item.hide_open_button"
type="checkbox"
data-testid="custom-menu-hide-open-button"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">
{{ t("admin.settings.customMenu.hideOpenButton") }}
</span>
</label>
<!-- SVG Icon (full width) -->
<div class="sm:col-span-2">
<label
@@ -9617,6 +9628,7 @@ const form = reactive<SettingsForm>({
url: string;
visibility: "user" | "admin";
sort_order: number;
hide_open_button?: boolean;
}>,
custom_endpoints: [] as Array<{
name: string;
@@ -720,6 +720,31 @@ describe("admin SettingsView payment visible method controls", () => {
adminSettingsFetch.mockResolvedValue(undefined);
});
it("loads and saves the open button visibility for each custom menu", async () => {
const menuItems = [
{ id: "docs", label: "Docs", url: "https://example.com/docs", icon_svg: "", visibility: "user", sort_order: 0 },
{ id: "help", label: "Help", url: "https://example.com/help", icon_svg: "", visibility: "user", sort_order: 1, hide_open_button: true },
];
getSettings.mockResolvedValue({ ...baseSettingsResponse, custom_menu_items: menuItems });
const wrapper = mountView();
await flushPromises();
const toggles = wrapper.findAll<HTMLInputElement>('[data-testid="custom-menu-hide-open-button"]');
expect(toggles.map(toggle => toggle.element.checked)).toEqual([false, true]);
await toggles[0].setValue(true);
await toggles[1].setValue(false);
await wrapper.find("form").trigger("submit.prevent");
await flushPromises();
expect(updateSettings).toHaveBeenCalledWith(expect.objectContaining({
custom_menu_items: [
{ ...menuItems[0], hide_open_button: true },
{ ...menuItems[1], hide_open_button: false },
],
}));
wrapper.unmount();
});
it("submits the compact home page toggle", async () => {
const wrapper = mountView();
await flushPromises();
@@ -96,6 +96,7 @@
<!-- Iframe embed mode -->
<div v-else ref="embedShell" class="custom-embed-shell">
<a
v-if="!menuItem?.hide_open_button"
ref="openButton"
:href="embeddedUrl"
target="_blank"
@@ -79,6 +79,13 @@ describe('custom page open button', () => {
vi.unstubAllGlobals()
})
it.each([undefined, false, true])('honors the per-menu hide button setting %s while keeping the iframe', (hidden) => {
Object.assign(appStore.cachedPublicSettings.custom_menu_items[0], { hide_open_button: hidden })
const wrapper = mountPage()
expect(wrapper.find('.custom-open-fab').exists()).toBe(hidden !== true)
expect(wrapper.get('iframe').attributes('src')).toContain('https://example.com/docs')
})
it('preserves the embedded URL, secure link attributes, and normal clicks with small pointer movements', async () => {
const { wrapper, button } = mountEmbed()
expect(button.href).toBe(wrapper.get('iframe').attributes('src'))