edit_on_github.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. (function(){
  2. function getMetaEditBase(){
  3. // 从 Sphinx html_context 注入的值读取
  4. try {
  5. var metas = document.getElementsByTagName('meta');
  6. for (var i=0;i<metas.length;i++){
  7. if (metas[i].getAttribute('name') === 'edit-base-url'){
  8. return (metas[i].getAttribute('content') || '').replace(/\/+$/, '/')
  9. }
  10. }
  11. } catch(e) {}
  12. return '';
  13. }
  14. function injectMetaFromContext(){
  15. // conf.py 会把 edit_base_url 放到 html_context;我们在模板层插一条 meta
  16. if (window && window.SPHINX_EDIT_BASE_URL && !document.querySelector('meta[name="edit-base-url"]')){
  17. var m = document.createElement('meta');
  18. m.setAttribute('name','edit-base-url');
  19. m.setAttribute('content', window.SPHINX_EDIT_BASE_URL);
  20. document.head.appendChild(m);
  21. }
  22. }
  23. function isEditablePage(){
  24. // 检查当前页面是否对应 Markdown 文件
  25. var path = location.pathname;
  26. var urlPath = (window.versionInfo && window.versionInfo.url_path) ? window.versionInfo.url_path : 'latest';
  27. var marker = '/' + urlPath + '/';
  28. var idx = path.indexOf(marker);
  29. if (idx < 0) return false; // 不在文档路径中
  30. var relativePath = path.substring(idx + marker.length);
  31. // 过滤掉索引页面和特殊页面
  32. if (!relativePath || relativePath === '' || relativePath === 'index.html') return false;
  33. if (relativePath.match(/^(search|genindex|modindex)\.html$/)) return false;
  34. if (relativePath.match(/\/(index\.html)?$/)) return false; // 目录索引页
  35. // 检查是否在项目目录结构中(包含至少两级路径,如 basic/project_name/README.html)
  36. var pathParts = relativePath.split('/');
  37. if (pathParts.length < 2) return false;
  38. // 检查对应的源文件是否是 Markdown 文件
  39. // 通过检查 copyFiles 中是否有对应的 .md 文件来判断
  40. var copyFiles = (window.versionInfo && window.versionInfo.copyFiles) ? window.versionInfo.copyFiles : [];
  41. var fileName = pathParts[pathParts.length - 1].replace(/\.html$/i, '').toLowerCase();
  42. // 检查 copyFiles 中是否有对应的 .md 文件
  43. var hasMarkdownFile = copyFiles.some(function(file) {
  44. var fileBaseName = String(file || '').replace(/\.md$/i, '').toLowerCase();
  45. return fileBaseName === fileName;
  46. });
  47. return hasMarkdownFile;
  48. }
  49. function detectCurrentLanguage() {
  50. var htmlLang = document.documentElement.getAttribute('lang');
  51. var currentPath = window.location.pathname;
  52. if ((htmlLang === 'zh-CN') && (currentPath.endsWith('_zh.html'))) return 'zh';
  53. if ((htmlLang === 'en') && (currentPath.endsWith('.html'))) return 'en';
  54. return 'en'; // 默认英文
  55. }
  56. function buildEditUrl(){
  57. var base = getMetaEditBase();
  58. // 规范化 base,确保为绝对 URL,避免生成相对路径
  59. function normalizeBase(s){
  60. if (!s) return '';
  61. s = (s + '').trim();
  62. if (/^https?:\/\//i.test(s)) return s.replace(/\/+$/, '/')
  63. if (/github\.com\//i.test(s)) return ('https://' + s).replace(/\/+$/, '/');
  64. return '';
  65. }
  66. base = normalizeBase(base);
  67. if (!base) {
  68. // 在 file:// 环境下没有配置 base,则不显示按钮
  69. if (location && location.protocol === 'file:') return '';
  70. }
  71. if (!base) return '';
  72. var branch = (window.versionInfo && window.versionInfo.branch) ? window.versionInfo.branch : 'master';
  73. // 当前页面在版本目录后的相对路径
  74. var path = location.pathname;
  75. var urlPath = (window.versionInfo && window.versionInfo.url_path) ? window.versionInfo.url_path : 'latest';
  76. // 仅截取版本目录后的相对路径(不含域名与构建根)
  77. var marker = '/' + urlPath + '/';
  78. var idx = path.indexOf(marker);
  79. if (idx >= 0){
  80. path = path.substring(idx + marker.length);
  81. }
  82. // 将 docs 拷贝后的路径映射回真实项目源路径
  83. // 示例:driver/Titan_driver_hyperram/README_zh.html -> project/Titan_driver_hyperram/README_zh.md
  84. var projectsDir = (window.versionInfo && window.versionInfo.projectsDir) ? window.versionInfo.projectsDir : '';
  85. var copyFiles = (window.versionInfo && window.versionInfo.copyFiles) ? window.versionInfo.copyFiles : [];
  86. var mapped = path;
  87. // 剥离 docs 分类前缀(start/basic/driver/component/...)后取剩余相对路径
  88. var firstSlash = mapped.indexOf('/');
  89. if (firstSlash >= 0) {
  90. mapped = mapped.substring(firstSlash + 1);
  91. }
  92. // 去掉 .html 后缀
  93. var htmlName = mapped.split('/').pop();
  94. var baseName = htmlName.replace(/\.html$/i, '');
  95. // 依据 copy_files 中的候选名推断源文件名优先级(README_zh.md、README.md 等)
  96. // 回退规则:
  97. // 1) 如果 baseName 匹配 README_zh/README 等,按列表顺序尝试;
  98. // 2) 否则将 .html 改为 .md
  99. var candidates = [];
  100. if (/^readme(_zh)?$/i.test(baseName)) {
  101. var normalized = copyFiles.map(function(n){ return String(n || '').toLowerCase(); });
  102. // 根据当前页面文件名匹配对应的源文件
  103. var targetFile = '';
  104. if (baseName.toLowerCase() === 'readme_zh') {
  105. targetFile = 'README_zh.md';
  106. } else if (baseName.toLowerCase() === 'readme') {
  107. targetFile = 'README.md';
  108. }
  109. // 如果找到匹配的目标文件且存在于 copyFiles 中,优先使用
  110. if (targetFile && normalized.indexOf(targetFile.toLowerCase()) >= 0) {
  111. candidates.push(targetFile);
  112. } else {
  113. // 回退到原有逻辑:常见文件名优先,保持原始大小写
  114. ['README_zh.md','README.md'].forEach(function(name){
  115. var lowerName = name.toLowerCase();
  116. if (normalized.indexOf(lowerName) >= 0 && candidates.indexOf(name) < 0) candidates.push(name);
  117. });
  118. }
  119. // 追加其余列表中文件名(保持原始大小写)
  120. copyFiles.forEach(function(n){
  121. var fileName = String(n || '');
  122. var ln = fileName.toLowerCase();
  123. if (candidates.map(function(c){return c.toLowerCase();}).indexOf(ln) < 0) candidates.push(fileName);
  124. });
  125. }
  126. if (candidates.length === 0) {
  127. candidates = [baseName + '.md'];
  128. }
  129. // 组装目录 + 源文件名
  130. var dir = mapped.substring(0, mapped.lastIndexOf('/'));
  131. mapped = dir + '/' + candidates[0];
  132. // 拼接为仓库中的项目路径
  133. if (projectsDir) {
  134. mapped = projectsDir.replace(/\/+$/,'') + '/' + mapped.replace(/^\//,'');
  135. }
  136. // 修复:正确构建 GitHub 编辑链接
  137. // 从 base URL 中提取 user 和 repo 信息
  138. // base 格式应该是类似 "https://github.com/user/repo/" 或 "github.com/user/repo/"
  139. var match = base.match(/github\.com\/([^\/]+)\/([^\/]+)\/?/i);
  140. if (!match) {
  141. console.warn('无法从 base URL 中解析 GitHub 用户名和仓库名:', base);
  142. return '';
  143. }
  144. var user = match[1];
  145. var repo = match[2];
  146. // 构建正确的 GitHub 编辑 URL 格式
  147. // https://github.com/$user/$repo/edit/$branch/$file_path
  148. var editUrl = 'https://github.com/' + user + '/' + repo + '/edit/' + branch + '/' + mapped;
  149. return editUrl;
  150. }
  151. function insertButton(){
  152. // 首先检查是否是可编辑的文档页面
  153. if (!isEditablePage()) return;
  154. var url = buildEditUrl();
  155. if (!url) return;
  156. // 检测当前页面语言
  157. var currentLang = detectCurrentLanguage();
  158. var buttonText = currentLang === 'zh' ? '在GitHub上编辑' : 'Edit on GitHub';
  159. var buttonTitle = currentLang === 'zh' ? '在GitHub上编辑此页面' : 'Edit this page on GitHub';
  160. // 优先插入到面包屑,保持与导航同高;使用带边框+图标样式
  161. var breadcrumbs = document.querySelector('.wy-breadcrumbs');
  162. if (breadcrumbs) {
  163. var oldAside = breadcrumbs.querySelector('.wy-breadcrumbs-aside');
  164. if (oldAside && oldAside.parentNode) oldAside.parentNode.removeChild(oldAside);
  165. var li = document.createElement('li');
  166. li.className = 'wy-breadcrumbs-aside';
  167. var a = document.createElement('a');
  168. a.href = url;
  169. a.target = '_blank';
  170. a.rel = 'noopener';
  171. a.className = 'sdk-edit-on-github__btn';
  172. a.innerHTML = '<svg class="sdk-edit-on-github__icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z"></path></svg><span>' + buttonText + '</span>';
  173. a.title = buttonTitle;
  174. li.appendChild(a);
  175. breadcrumbs.appendChild(li);
  176. return;
  177. }
  178. // 回退:插入到正文容器顶部右侧
  179. var content = document.querySelector('.wy-nav-content');
  180. if (!content) return;
  181. if (document.querySelector('.sdk-edit-on-github')) return;
  182. var container = document.createElement('div');
  183. container.className = 'sdk-edit-on-github';
  184. var a2 = document.createElement('a');
  185. a2.className = 'sdk-edit-on-github__btn';
  186. a2.target = '_blank';
  187. a2.rel = 'noopener';
  188. a2.href = url;
  189. a2.innerHTML = '<svg class="sdk-edit-on-github__icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z"></path></svg><span>' + buttonText + '</span>';
  190. a2.title = buttonTitle;
  191. container.appendChild(a2);
  192. var first = content.firstElementChild;
  193. if (first) content.insertBefore(container, first); else content.appendChild(container);
  194. }
  195. function init(){
  196. injectMetaFromContext();
  197. if (document.readyState === 'loading'){
  198. document.addEventListener('DOMContentLoaded', insertButton);
  199. } else {
  200. insertButton();
  201. }
  202. }
  203. init();
  204. })();