version_menu.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. // Read the Docs 风格的版本菜单功能
  2. (function() {
  3. 'use strict';
  4. // 版本配置 - 将从版本配置文件获取
  5. let VERSION_CONFIG = {
  6. current: 'main',
  7. versions: {}
  8. };
  9. // 从版本配置文件获取版本信息
  10. // 获取嵌入的版本配置(在构建时由脚本生成)
  11. function getEmbeddedVersionConfig() {
  12. return {
  13. "versions": [
  14. {
  15. "name": "master",
  16. "display_name": "latest",
  17. "branch": "main",
  18. "url_path": "latest",
  19. "description": "最新开发版本"
  20. }
  21. ],
  22. "default_version": "master",
  23. "latest_version": "master"
  24. };
  25. }
  26. async function fetchVersionInfo() {
  27. try {
  28. // 智能检测当前版本
  29. const detectedVersion = detectCurrentVersion();
  30. console.log('检测到的当前版本:', detectedVersion);
  31. // 检查是否为本地文件系统
  32. const isLocalFileSystem = window.location.protocol === 'file:';
  33. console.log('是否为本地文件系统:', isLocalFileSystem);
  34. let config = null;
  35. let configPath = '';
  36. console.log('本地文件系统,使用嵌入的版本配置');
  37. const embeddedConfig = getEmbeddedVersionConfig();
  38. if (embeddedConfig) {
  39. config = embeddedConfig;
  40. configPath = 'embedded';
  41. console.log('使用嵌入的版本配置');
  42. } else {
  43. console.log('嵌入配置不可用,尝试其他方法');
  44. }
  45. // 如果仍然没有配置,使用嵌入的配置作为兜底
  46. if (!config) {
  47. console.log('尝试使用嵌入的版本配置作为兜底');
  48. const embeddedConfig = getEmbeddedVersionConfig();
  49. if (embeddedConfig) {
  50. config = embeddedConfig;
  51. configPath = 'embedded_fallback';
  52. console.log('使用嵌入的版本配置作为兜底');
  53. }
  54. }
  55. if (!config) {
  56. throw new Error('无法找到版本配置文件');
  57. }
  58. // 确定当前版本
  59. const currentPath = window.location.pathname;
  60. let currentVersion = config.default_version || 'main';
  61. // 从URL路径判断当前版本
  62. for (const version of config.versions) {
  63. if (currentPath.includes(`/${version.url_path}/`)) {
  64. currentVersion = version.name;
  65. break;
  66. }
  67. }
  68. // 构建版本配置对象
  69. const versions = {};
  70. for (const version of config.versions) {
  71. versions[version.name] = {
  72. display_name: version.display_name,
  73. url_path: version.url_path,
  74. description: version.description
  75. };
  76. }
  77. VERSION_CONFIG = {
  78. current: currentVersion,
  79. versions: versions
  80. };
  81. console.log('版本信息已更新:', VERSION_CONFIG);
  82. console.log('配置文件路径:', configPath);
  83. // 重新创建版本菜单
  84. createVersionMenu();
  85. } catch (error) {
  86. console.warn('无法获取版本信息,使用默认配置:', error);
  87. // 使用默认配置
  88. VERSION_CONFIG = {
  89. current: 'main',
  90. versions: {
  91. 'main': {
  92. display_name: '最新版本',
  93. url_path: 'latest',
  94. description: '最新开发版本'
  95. }
  96. }
  97. };
  98. createVersionMenu();
  99. }
  100. }
  101. // 智能检测当前版本
  102. function detectCurrentVersion() {
  103. const pathname = window.location.pathname;
  104. console.log('当前路径:', pathname);
  105. // 检查是否在版本子目录中
  106. const versionMatch = pathname.match(/\/(v\d+\.\d+|latest)\//);
  107. if (versionMatch) {
  108. const detectedVersion = versionMatch[1];
  109. console.log('检测到版本目录:', detectedVersion);
  110. return detectedVersion;
  111. }
  112. // 检查URL参数
  113. const urlParams = new URLSearchParams(window.location.search);
  114. const versionParam = urlParams.get('version');
  115. if (versionParam) {
  116. console.log('从URL参数检测到版本:', versionParam);
  117. return versionParam;
  118. }
  119. console.log('未检测到版本信息,使用默认路径');
  120. return null;
  121. }
  122. // 创建版本菜单
  123. function createVersionMenu() {
  124. // 查找侧边栏 - 改进查找逻辑
  125. let sidebar = document.querySelector('.wy-nav-side');
  126. if (!sidebar) {
  127. // 尝试其他可能的选择器
  128. sidebar = document.querySelector('nav[data-toggle="wy-nav-shift"]');
  129. }
  130. if (!sidebar) {
  131. sidebar = document.querySelector('.wy-side-scroll');
  132. }
  133. if (!sidebar) {
  134. console.warn('找不到侧边栏元素,尝试在body中创建版本菜单');
  135. sidebar = document.body;
  136. }
  137. // 检查是否已经存在版本菜单
  138. if (document.querySelector('.rtd-version-menu')) {
  139. console.log('版本菜单已存在,跳过创建');
  140. return;
  141. }
  142. // 计算最长版本名称的宽度
  143. const versionNames = Object.values(VERSION_CONFIG.versions).map(v => v.display_name);
  144. const maxLength = Math.max(...versionNames.map(name => name.length));
  145. const minWidth = Math.max(80, maxLength * 5 + 20);
  146. // 创建版本菜单容器
  147. const versionMenu = document.createElement('div');
  148. versionMenu.className = 'rtd-version-menu';
  149. versionMenu.style.minWidth = minWidth + 'px';
  150. versionMenu.innerHTML = `
  151. <button class="rtd-version-menu__button" type="button" aria-haspopup="true" aria-expanded="false">
  152. <span class="rtd-version-menu__current">${VERSION_CONFIG.versions[VERSION_CONFIG.current]?.display_name || '版本'}</span>
  153. </button>
  154. <div class="rtd-version-menu__dropdown" role="menu">
  155. ${Object.entries(VERSION_CONFIG.versions).map(([version, versionInfo]) => `
  156. <a class="rtd-version-menu__item ${version === VERSION_CONFIG.current ? 'active' : ''}"
  157. href="#" data-version="${version}" role="menuitem">
  158. ${versionInfo.display_name}
  159. </a>
  160. `).join('')}
  161. </div>
  162. `;
  163. // 尝试不同的插入位置
  164. let inserted = false;
  165. // 1. 尝试插入到项目标题下方
  166. const projectTitle = sidebar.querySelector('a.icon.icon-home');
  167. if (projectTitle && projectTitle.parentNode) {
  168. projectTitle.parentNode.insertBefore(versionMenu, projectTitle.nextSibling);
  169. inserted = true;
  170. console.log('版本菜单已插入到项目标题下方');
  171. }
  172. // 2. 尝试插入到搜索框下方
  173. if (!inserted) {
  174. const searchForm = sidebar.querySelector('#rtd-search-form');
  175. if (searchForm && searchForm.parentNode) {
  176. searchForm.parentNode.insertBefore(versionMenu, searchForm.nextSibling);
  177. inserted = true;
  178. console.log('版本菜单已插入到搜索框下方');
  179. }
  180. }
  181. // 3. 尝试插入到侧边栏顶部
  182. if (!inserted) {
  183. const firstChild = sidebar.firstChild;
  184. if (firstChild) {
  185. sidebar.insertBefore(versionMenu, firstChild);
  186. inserted = true;
  187. console.log('版本菜单已插入到侧边栏顶部');
  188. }
  189. }
  190. // 4. 如果都失败了,直接添加到侧边栏
  191. if (!inserted) {
  192. sidebar.appendChild(versionMenu);
  193. console.log('版本菜单已添加到侧边栏');
  194. }
  195. // 添加事件监听器
  196. setupVersionMenuEvents(versionMenu);
  197. }
  198. // 设置版本菜单事件
  199. function setupVersionMenuEvents(versionMenu) {
  200. const button = versionMenu.querySelector('.rtd-version-menu__button');
  201. const dropdown = versionMenu.querySelector('.rtd-version-menu__dropdown');
  202. if (!button || !dropdown) {
  203. console.warn('找不到版本菜单按钮或下拉菜单');
  204. return;
  205. }
  206. // 按钮点击事件
  207. button.addEventListener('click', function(e) {
  208. e.preventDefault();
  209. e.stopPropagation();
  210. const isExpanded = this.getAttribute('aria-expanded') === 'true';
  211. this.setAttribute('aria-expanded', !isExpanded);
  212. dropdown.classList.toggle('show');
  213. console.log('版本菜单切换:', !isExpanded);
  214. });
  215. // 版本选择事件
  216. dropdown.addEventListener('click', function(e) {
  217. if (e.target.classList.contains('rtd-version-menu__item')) {
  218. e.preventDefault();
  219. e.stopPropagation();
  220. const version = e.target.getAttribute('data-version');
  221. const versionInfo = VERSION_CONFIG.versions[version];
  222. if (!versionInfo) {
  223. console.error('找不到版本信息:', version);
  224. return;
  225. }
  226. const versionName = versionInfo.display_name;
  227. // 更新当前版本显示
  228. const currentSpan = button.querySelector('.rtd-version-menu__current');
  229. if (currentSpan) {
  230. currentSpan.textContent = versionName;
  231. }
  232. // 更新活动状态
  233. dropdown.querySelectorAll('.rtd-version-menu__item').forEach(item => {
  234. item.classList.remove('active');
  235. });
  236. e.target.classList.add('active');
  237. // 关闭下拉菜单
  238. button.setAttribute('aria-expanded', 'false');
  239. dropdown.classList.remove('show');
  240. // 版本切换逻辑
  241. handleVersionChange(version, versionName);
  242. }
  243. });
  244. // 点击外部关闭下拉菜单
  245. document.addEventListener('click', function(e) {
  246. if (!versionMenu.contains(e.target)) {
  247. button.setAttribute('aria-expanded', 'false');
  248. dropdown.classList.remove('show');
  249. }
  250. });
  251. // 键盘导航支持
  252. button.addEventListener('keydown', function(e) {
  253. if (e.key === 'Enter' || e.key === ' ') {
  254. e.preventDefault();
  255. this.click();
  256. }
  257. });
  258. dropdown.addEventListener('keydown', function(e) {
  259. const items = Array.from(this.querySelectorAll('.rtd-version-menu__item'));
  260. const currentIndex = items.indexOf(document.activeElement);
  261. switch (e.key) {
  262. case 'ArrowDown':
  263. e.preventDefault();
  264. const nextIndex = (currentIndex + 1) % items.length;
  265. items[nextIndex].focus();
  266. break;
  267. case 'ArrowUp':
  268. e.preventDefault();
  269. const prevIndex = (currentIndex - 1 + items.length) % items.length;
  270. items[prevIndex].focus();
  271. break;
  272. case 'Escape':
  273. e.preventDefault();
  274. button.setAttribute('aria-expanded', 'false');
  275. dropdown.classList.remove('show');
  276. button.focus();
  277. break;
  278. }
  279. });
  280. }
  281. // 处理版本切换
  282. function handleVersionChange(version, versionName) {
  283. console.log('切换到版本:', version, versionName);
  284. // 获取当前页面的相对路径
  285. const currentPath = window.location.pathname;
  286. const baseUrl = window.location.origin;
  287. // 检查是否为本地开发环境
  288. const isLocalDev = window.location.hostname === 'localhost' ||
  289. window.location.hostname === '127.0.0.1' ||
  290. window.location.protocol === 'file:';
  291. // 获取目标版本的URL路径
  292. const targetVersionInfo = VERSION_CONFIG.versions[version];
  293. if (!targetVersionInfo) {
  294. console.error('找不到目标版本信息:', version);
  295. return;
  296. }
  297. const targetUrlPath = targetVersionInfo.url_path;
  298. // 简化URL构建逻辑
  299. let newUrl;
  300. if (isLocalDev) {
  301. // 本地开发环境:简化路径处理
  302. const currentUrlPath = getCurrentVersionPath();
  303. if (currentUrlPath) {
  304. // 替换版本目录
  305. const newPath = currentPath.replace(`/${currentUrlPath}/`, `/${targetUrlPath}/`);
  306. newUrl = `file://${newPath}`;
  307. } else {
  308. // 如果不在版本目录中,构建到版本目录的路径
  309. const fileName = currentPath.split('/').pop();
  310. const directory = currentPath.substring(0, currentPath.lastIndexOf('/'));
  311. newUrl = `file://${directory}/${targetUrlPath}/${fileName}`;
  312. }
  313. } else {
  314. // 生产环境:简化路径处理
  315. const relativePath = getRelativePathFromCurrentVersion();
  316. const repoName = getRepoNameFromPath() || 'sdk-bsp-rzn2l-etherkit';
  317. newUrl = `${baseUrl}/${repoName}/${targetUrlPath}/${relativePath}`;
  318. // 确保URL以 / 结尾(如果是根路径)
  319. if (!relativePath || relativePath === '/') {
  320. newUrl = newUrl.replace(/\/$/, '') + '/';
  321. }
  322. }
  323. console.log('当前路径:', currentPath);
  324. console.log('是否为本地开发:', isLocalDev);
  325. console.log('跳转到:', newUrl);
  326. // 跳转到新版本
  327. window.location.href = newUrl;
  328. // 发送自定义事件
  329. const event = new CustomEvent('version-changed', {
  330. detail: {
  331. version: version,
  332. versionName: versionName,
  333. newUrl: newUrl
  334. }
  335. });
  336. document.dispatchEvent(event);
  337. }
  338. // 获取当前版本路径
  339. function getCurrentVersionPath() {
  340. const currentPath = window.location.pathname;
  341. for (const [verName, verInfo] of Object.entries(VERSION_CONFIG.versions)) {
  342. if (currentPath.includes(`/${verInfo.url_path}/`)) {
  343. return verInfo.url_path;
  344. }
  345. }
  346. return null;
  347. }
  348. // 从当前版本路径中提取相对路径
  349. function getRelativePathFromCurrentVersion() {
  350. const currentPath = window.location.pathname;
  351. for (const [verName, verInfo] of Object.entries(VERSION_CONFIG.versions)) {
  352. if (currentPath.includes(`/${verInfo.url_path}/`)) {
  353. const parts = currentPath.split(`/${verInfo.url_path}/`);
  354. return parts.length > 1 ? parts[1] : '';
  355. }
  356. }
  357. return '';
  358. }
  359. // 从路径中提取仓库名称
  360. function getRepoNameFromPath() {
  361. const currentPath = window.location.pathname;
  362. const pathParts = currentPath.split('/');
  363. for (let i = 0; i < pathParts.length - 1; i++) {
  364. if (pathParts[i] && pathParts[i + 1]) {
  365. const isVersionPath = Object.values(VERSION_CONFIG.versions).some(v => v.url_path === pathParts[i + 1]);
  366. if (isVersionPath) {
  367. return pathParts[i];
  368. }
  369. }
  370. }
  371. return null;
  372. }
  373. // 初始化版本菜单
  374. function init() {
  375. console.log('初始化版本菜单...');
  376. // 等待 DOM 加载完成
  377. if (document.readyState === 'loading') {
  378. document.addEventListener('DOMContentLoaded', fetchVersionInfo);
  379. } else {
  380. fetchVersionInfo();
  381. }
  382. // 如果侧边栏是动态加载的,等待一下再尝试创建
  383. setTimeout(() => {
  384. if (!document.querySelector('.rtd-version-menu')) {
  385. createVersionMenu();
  386. }
  387. }, 1000);
  388. setTimeout(() => {
  389. if (!document.querySelector('.rtd-version-menu')) {
  390. createVersionMenu();
  391. }
  392. }, 2000);
  393. setTimeout(() => {
  394. if (!document.querySelector('.rtd-version-menu')) {
  395. createVersionMenu();
  396. }
  397. }, 3000);
  398. }
  399. // 启动
  400. init();
  401. })();