version_menu.js 17 KB

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