两套网页可通过 runtime-config.js 的 VITE_COMPACT_MENU_TYPE 加载各自左侧菜单;缺省或旧页面不传参数时仍读取原来的类型 A 数据。 Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import type { MenuItem } from "./sidebar-menu";
|
|
import { readRuntimeEnv } from "@/env";
|
|
|
|
export type CompactMenuPlacement = "sidebar" | "settings";
|
|
|
|
export const COMPACT_MENU_TYPES = ["A", "B"] as const;
|
|
export type CompactMenuType = (typeof COMPACT_MENU_TYPES)[number];
|
|
export const DEFAULT_COMPACT_MENU_TYPE: CompactMenuType = "A";
|
|
|
|
const TYPE_ALIASES: Record<string, CompactMenuType> = {
|
|
A: "A",
|
|
"1": "A",
|
|
DEFAULT: "A",
|
|
B: "B",
|
|
"2": "B",
|
|
ALT: "B",
|
|
};
|
|
|
|
export function normalizeCompactMenuType(value?: string | null): CompactMenuType {
|
|
const raw = (value ?? "").trim().toUpperCase();
|
|
return TYPE_ALIASES[raw] ?? DEFAULT_COMPACT_MENU_TYPE;
|
|
}
|
|
|
|
export function isDefaultCompactMenuType(value?: string | null): boolean {
|
|
return normalizeCompactMenuType(value) === DEFAULT_COMPACT_MENU_TYPE;
|
|
}
|
|
|
|
/** Compact-menu variant for this website, from runtime-config.js.
|
|
* Missing / empty config is type A, which loads the original compact menu. */
|
|
export function getRuntimeCompactMenuType(): CompactMenuType {
|
|
return normalizeCompactMenuType(readRuntimeEnv("VITE_COMPACT_MENU_TYPE"));
|
|
}
|
|
|
|
export const COMPACT_MENU_TYPE_LABELS: Record<CompactMenuType, string> = {
|
|
A: "类型 A",
|
|
B: "类型 B",
|
|
};
|
|
|
|
export interface CompactMenuOverride {
|
|
nodeId: string;
|
|
placement: CompactMenuPlacement;
|
|
sortOrder: number;
|
|
label?: string | null;
|
|
}
|
|
|
|
export interface CompactMenuItem extends MenuItem {
|
|
compactPlacement: CompactMenuPlacement;
|
|
compactOrder: number;
|
|
}
|
|
|
|
function firstRoutePath(items?: MenuItem[]): string | undefined {
|
|
for (const item of items ?? []) {
|
|
if (item.path) return item.path;
|
|
const nested = firstRoutePath(item.children);
|
|
if (nested) return nested;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function isNavigable(item: MenuItem): boolean {
|
|
return Boolean(item.path ?? firstRoutePath(item.children));
|
|
}
|
|
|
|
export function applyCompactMenuOverrides(
|
|
items: MenuItem[],
|
|
overrides: CompactMenuOverride[],
|
|
): { sidebarItems: CompactMenuItem[]; settingsItems: CompactMenuItem[] } {
|
|
const byId = new Map(overrides.map((override) => [override.nodeId, override]));
|
|
const enhanced = items.filter(isNavigable).map((item, index) => {
|
|
const override = byId.get(item.id);
|
|
return {
|
|
...item,
|
|
label: override?.label != null && override.label !== "" ? override.label : item.label,
|
|
compactPlacement: override?.placement ?? "sidebar",
|
|
compactOrder: override?.sortOrder ?? index,
|
|
};
|
|
});
|
|
|
|
const sortItems = (list: CompactMenuItem[]) =>
|
|
list.slice().sort((a, b) => a.compactOrder - b.compactOrder || a.id.localeCompare(b.id));
|
|
|
|
return {
|
|
sidebarItems: sortItems(enhanced.filter((item) => item.compactPlacement === "sidebar")),
|
|
settingsItems: sortItems(enhanced.filter((item) => item.compactPlacement === "settings")),
|
|
};
|
|
}
|
|
|
|
export function diffCompactMenuOverrides(items: CompactMenuEditorItem[]): CompactMenuOverride[] {
|
|
return items.flatMap((item, index) => {
|
|
const renamed = item.label !== item.defaultLabel;
|
|
const moved = item.compactPlacement !== "sidebar";
|
|
const reordered = index !== item.defaultIndex || item.compactOrder !== item.defaultIndex;
|
|
if (!renamed && !moved && !reordered) return [];
|
|
return {
|
|
nodeId: item.id,
|
|
placement: item.compactPlacement,
|
|
sortOrder: index,
|
|
label: renamed ? item.label : null,
|
|
};
|
|
});
|
|
}
|
|
|
|
export interface CompactMenuEditorItem extends CompactMenuItem {
|
|
defaultLabel: string;
|
|
defaultIndex: number;
|
|
}
|