Easy Swagger

注意:此脚本为自用包,请搜索 swagger-toolkit 安装原作者的脚本

  1. // ==UserScript==
  2. // @name Easy Swagger
  3. // @namespace https://github.com/peihaojie/Greasemonkey-script
  4. // @description 注意:此脚本为自用包,请搜索 swagger-toolkit 安装原作者的脚本
  5. // @description 注意: 需要增加适配网站,请手动修改 @include
  6. // @include https://test-dms.skyallhere.com/api/swagger-ui/index.html
  7. // @version 1.0
  8. // @icon https://raw.githubusercontent.com/peihaojie/Greasemonkey-script/master/icon.png
  9. // ==/UserScript==
  10.  
  11. class Sheets {
  12. static sheets = `
  13. body {
  14. --row-width: 13vw;
  15. --row-min-width: 245px;
  16. --row-title-font-size: 14px;
  17. --body-wrapper-width: 80vw;
  18. --body-wrapper-margin-right: 3vw;
  19. --body-wrapper-min-width: 800px;
  20. --body-btn-group-width: 20px;
  21. }
  22.  
  23. /* 应用于 Copy input */
  24. .toolkit-hidden { width: 1; height: 1; }
  25.  
  26. /* 接口信息部分样式 */
  27. #swagger-ui .opblock .toolkit-path-btn-group { margin-left: 10px; display: none; }
  28. #swagger-ui .opblock:hover .toolkit-path-btn-group { display: block; }
  29. #swagger-ui .opblock .toolkit-path-btn-group a { text-decoration: none; }
  30.  
  31. /* 页面内容主体布局 */
  32. #swagger-ui div.topbar { display: flex; justify-content: flex-end; }
  33. #swagger-ui div.topbar .wrapper { margin: 0; width: var(--body-wrapper-width); min-width: var(--body-wrapper-min-width); margin-right: var(--body-wrapper-margin-right) }
  34. #swagger-ui div.swagger-ui { display: flex; justify-content: flex-end; }
  35. #swagger-ui div.swagger-ui .wrapper { margin: 0; width: var(--body-wrapper-width); min-width: var(--body-wrapper-min-width); margin-right: var(--body-wrapper-margin-right) }
  36.  
  37. /* sidebar part */
  38. #swagger-toolkit-sidebar {
  39. width: var(--row-width);
  40. min-width: var(--row-min-width);
  41. display: flex;
  42. position: fixed;
  43. top: 0;
  44. left: 0;
  45. height: 100vh;
  46. flex-direction: column;
  47. justify-content: space-between;
  48. background-color: #FAFAFA;
  49. border-right: 1px solid #c4d6d6;
  50. }
  51. #swagger-toolkit-sidebar .list { width: 100%; }
  52. #swagger-toolkit-sidebar .list > header { font-size: 18px; background-color: #999; }
  53. #swagger-toolkit-sidebar .list > header > .title { color: #FFF; text-align: center; font-weight: 200; }
  54. #swagger-toolkit-sidebar .row { display: flex; padding-bottom: 5px; width: 100%; cursor: pointer; text-decoration: none; }
  55. #swagger-toolkit-sidebar .row.method-DELETE { background-color: rgba(249,62,62,.1); }
  56. #swagger-toolkit-sidebar .row.method-DELETE:hover { background-color: rgba(249,62,62,.5); }
  57. #swagger-toolkit-sidebar .row.method-GET { background-color: rgba(97,175,254,.1); }
  58. #swagger-toolkit-sidebar .row.method-GET:hover { background-color: rgba(97,175,254,.5); }
  59. #swagger-toolkit-sidebar .row.method-POST { background-color: rgba(73,204,144,.1); }
  60. #swagger-toolkit-sidebar .row.method-POST:hover { background-color: rgba(73,204,144,.5); }
  61. #swagger-toolkit-sidebar .row.method-PUT { background-color: rgba(252,161,48,.1); }
  62. #swagger-toolkit-sidebar .row.method-PUT:hover { background-color: rgba(252,161,48,.5); }
  63. #swagger-toolkit-sidebar .row.method-PATCH { background-color: rgba(80,227,194,.1); }
  64. #swagger-toolkit-sidebar .row.method-PATCH:hover { background-color: rgba(80,227,194,.5); }
  65.  
  66. #swagger-toolkit-sidebar .row .description { color: #333; font-size: 14px; width: calc(var(--row-width) - var(--body-btn-group-width)); min-width: calc(var(--row-min-width) - var(--body-btn-group-width)); }
  67. #swagger-toolkit-sidebar .row .method { display: flex; line-height: 45px; min-width: 64px; }
  68. #swagger-toolkit-sidebar .row .path > a { color: #409EFF; }
  69.  
  70. #swagger-toolkit-sidebar .row .btn-group { font-size: 12px; }
  71. #swagger-toolkit-sidebar .row .btn-group > a { text-decoration: none; display: block; }
  72. #swagger-toolkit-sidebar .row .btn-group > a:hover { font-size: 14px; }
  73.  
  74. /* helper */
  75. .tool-text-size-fixed { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  76. `;
  77. static inject() {
  78. const sheet = document.createTextNode(Sheets.sheets);
  79. const el = document.createElement("style");
  80. el.id = "swagger-toolkit-sheets";
  81. el.appendChild(sheet);
  82. document.getElementsByTagName("head")[0].appendChild(el);
  83. }
  84. }
  85. class LinkStore {
  86. key = "";
  87. path = "";
  88. method = "";
  89. description = ""; // 接口名
  90. id = "";
  91. createdat = 0;
  92. static MAX_LENGTH = 10;
  93. static save(row, key) {
  94. const store = new LinkStore();
  95. store.id = row.id;
  96. store.key = key;
  97. store.method = row.querySelector(".opblock-summary-method").innerText;
  98. store.path = row.querySelector(".opblock-summary-path > a").innerText;
  99. store.description = row.querySelector(
  100. ".opblock-summary-description"
  101. ).innerText;
  102. LinkStore.add(key, store);
  103. }
  104. static add(key, store, filterRepeat) {
  105. let data = LinkStore.getStore(key);
  106. if (filterRepeat) {
  107. for (const row of data) {
  108. if (row.id === store.id && store.path === store.path) return false;
  109. }
  110. }
  111. data.unshift(store);
  112. if (data.length > LinkStore.MAX_LENGTH)
  113. data = data.slice(0, LinkStore.MAX_LENGTH);
  114. localStorage.setItem(key, JSON.stringify(data));
  115. }
  116. static remove(key, index) {
  117. let data = LinkStore.getStore(key);
  118. data.splice(index, 1);
  119. localStorage.setItem(key, JSON.stringify(data));
  120. }
  121. static getStore(key) {
  122. let store = [];
  123. try {
  124. const _store = localStorage.getItem(key);
  125. if (_store) store = JSON.parse(_store);
  126. } catch (err) {
  127. console.error(err);
  128. }
  129. return store;
  130. }
  131. }
  132. class Pane {
  133. dom = null;
  134. localKey = null;
  135. title = null;
  136. placeholder = "暂无数据";
  137. placeholder_en = "no data";
  138. btnSave = "收藏";
  139. btnSave_en = "add to favorites";
  140. btnRemove = "删除";
  141. btnRemove_en = "remove";
  142. enableMarkBtn = false;
  143. /**
  144. * 生成或更新当前 Pane
  145. * @description 将生成 `.list>(header>.title)+(a.row>(.method+.contents>(.description+a.path)))`
  146. */
  147. generateDom(isUpdate) {
  148. if (isUpdate) this.dom.innerHTML = "";
  149. const list = isUpdate ? this.dom : document.createElement("div");
  150. list.classList.add("list");
  151. list.classList.add(this.localKey);
  152. list.setAttribute("data-key", this.localKey);
  153. // 添加 header
  154. const header = document.createElement("header");
  155. const title = document.createElement("div");
  156. title.classList.add("title");
  157. title.innerText = this.getLabelByLanguage("title");
  158. list.appendChild(header);
  159. header.appendChild(title);
  160. // 添加数据
  161. const data = LinkStore.getStore(this.localKey);
  162. for (const dataRow of data) {
  163. const row = document.createElement("a");
  164. row.href = "#" + dataRow.id;
  165. row.setAttribute("data-row", JSON.stringify(dataRow));
  166. const method = document.createElement("div");
  167. method.innerText = dataRow.method;
  168. const contents = document.createElement("div");
  169. const description = document.createElement("div");
  170. description.innerText = dataRow.description;
  171. const path = document.createElement("div");
  172. const pathLink = document.createElement("a");
  173. pathLink.innerText = dataRow.path;
  174. pathLink.href = "#" + dataRow.id;
  175. const btnGroup = document.createElement("div");
  176. const markBtn = document.createElement("a");
  177. if (this.enableMarkBtn) {
  178. markBtn.href = "javascript:;";
  179. markBtn.setAttribute("title", this.getLabelByLanguage("btnSave"));
  180. markBtn.innerText = "⭐️";
  181. }
  182. const deleteBtn = document.createElement("a");
  183. deleteBtn.href = "javascript:;";
  184. deleteBtn.setAttribute("title", this.getLabelByLanguage("btnRemove"));
  185. deleteBtn.innerText = "✖️";
  186.  
  187. row.classList.add("row");
  188. row.classList.add("method-" + dataRow.method);
  189. method.classList.add("method");
  190. contents.classList.add("contents");
  191. description.classList.add("description");
  192. description.classList.add("tool-text-size-fixed");
  193. path.classList.add("path");
  194. btnGroup.classList.add("btn-group");
  195. if (this.enableMarkBtn) markBtn.classList.add("btn-mark");
  196. deleteBtn.classList.add("btn-delete");
  197.  
  198. path.appendChild(pathLink);
  199. contents.appendChild(description);
  200. contents.appendChild(path);
  201. // row.appendChild(method)
  202. row.appendChild(contents);
  203. row.appendChild(btnGroup);
  204. btnGroup.appendChild(deleteBtn);
  205. if (this.enableMarkBtn) btnGroup.appendChild(markBtn);
  206. list.appendChild(row);
  207. }
  208. if (data.length === 0) list.appendChild(this.getPlaceholderDom());
  209. this.dom = list;
  210. if (typeof this.afterGenerageDom === "function") this.afterGenerageDom();
  211. return list;
  212. }
  213. getPlaceholderDom() {
  214. const dom = document.createElement("section");
  215. dom.innerText = this.getLabelByLanguage("placeholder");
  216. return dom;
  217. }
  218. getLabelByLanguage(field, language) {
  219. let lang = language;
  220. if (!lang) {
  221. const _lang = navigator.language;
  222. lang = _lang.indexOf("zh") === 0 ? "" : "en";
  223. }
  224. return this[`${field}${lang ? "_" + lang : ""}`];
  225. }
  226. }
  227. class HistoryPane extends Pane {
  228. localKey = "swagger-toolkit-history";
  229. title = "浏览历史";
  230. title_en = "History";
  231. placeholder = "暂无浏览历史数据";
  232. placeholder_en = "No history at present";
  233. enableMarkBtn = true;
  234. }
  235. class MarkPane extends Pane {
  236. localKey = "swagger-toolkit-mark";
  237. title = "收藏夹";
  238. title_en = "Favorites";
  239. placeholder = "暂无收藏数据, 点击 ⭐️ 按钮添加";
  240. placeholder_en = "No favorite data, click ⭐️ button to add";
  241. afterGenerageDom() {
  242. this.dom;
  243. }
  244. }
  245. class SideBar {
  246. static dom = null;
  247. static panes = [];
  248. static pathBtnGroupClassName = "toolkit-path-btn-group";
  249. static copyInput = document.createElement("input");
  250. initCopyDOM() {
  251. SideBar.copyInput.classList.add("toolkit-hidden");
  252. document.body.appendChild(SideBar.copyInput);
  253. return this;
  254. }
  255. addListeners() {
  256. window.addEventListener("hashchange", () => {
  257. let _path = location.hash.length > 0 ? location.hash.substr(1) : "";
  258. if (!_path) return;
  259. _path = (window.decodeURI && window.decodeURI(_path)) || _path;
  260. const row =
  261. document.getElementById(_path) ||
  262. (document.querySelector(`a[href="#${_path}"]`) &&
  263. document.querySelector(`a[href="#${_path}"]`).closest(".opblock"));
  264. if (row) LinkStore.save(row, "swagger-toolkit-history");
  265. this._updatePane("swagger-toolkit-history");
  266. });
  267. document
  268. .querySelector("#swagger-ui")
  269. .addEventListener("mouseover", (evt) => {
  270. this._showPathBtnGroup(evt); // 显示在 path 栏中的按钮组
  271. });
  272. return this;
  273. }
  274. _showPathBtnGroup(evt) {
  275. const opblock = evt.target.closest(".opblock");
  276. if (!opblock) return;
  277. this._appendPathBtnGroupDOM(opblock);
  278. }
  279. _appendPathBtnGroupDOM(opblock) {
  280. if (opblock.querySelector("." + SideBar.pathBtnGroupClassName)) return;
  281. const group = document.createElement("div");
  282. const copyBtn = document.createElement("a");
  283. group.classList.add(SideBar.pathBtnGroupClassName);
  284. copyBtn.setAttribute("href", "javascript:;");
  285. copyBtn.classList.add("btn-copy");
  286. copyBtn.innerText = "🔗";
  287. copyBtn.setAttribute("title", "copy");
  288. group.appendChild(copyBtn);
  289. copyBtn.addEventListener("click", (evt) => {
  290. this._copyPath(evt);
  291. });
  292.  
  293. const pathDOM = opblock.querySelector(".opblock-summary-path");
  294. if (pathDOM) pathDOM.appendChild(group);
  295. }
  296. _copyPath(evt) {
  297. evt.stopPropagation();
  298. const pathDOM = evt.target.closest(".opblock-summary-path");
  299. if (!pathDOM) return;
  300. const pathLink = pathDOM.querySelector("a");
  301. if (!pathLink) return;
  302. const path = pathLink.innerText;
  303. SideBar.copyInput.value = path;
  304. SideBar.copyInput.select();
  305. document.execCommand("Copy");
  306. console.log("copy successfuly");
  307. }
  308. generateDom() {
  309. const sidebar = document.createElement("sidebar");
  310. sidebar.id = "swagger-toolkit-sidebar";
  311. SideBar.dom = sidebar;
  312. return this;
  313. }
  314. inject() {
  315. document.body.appendChild(SideBar.dom);
  316. return this;
  317. }
  318. appendPanes() {
  319. for (const pane of SideBar.panes) {
  320. SideBar.dom.appendChild(pane.generateDom());
  321. }
  322. return this;
  323. }
  324. _updatePane(key) {
  325. for (const pane of SideBar.panes) {
  326. if (pane.localKey !== key) continue;
  327. pane.generateDom(true);
  328. }
  329. }
  330. appendPanesListeners() {
  331. SideBar.dom.addEventListener("click", (evt) => {
  332. if (evt.target.classList.contains("btn-delete")) {
  333. evt.preventDefault();
  334. evt.stopPropagation();
  335. const index = this._getRowIndex({ btnItem: evt.target });
  336. const key =
  337. evt.target.parentNode.parentNode.parentNode.getAttribute("data-key");
  338. LinkStore.remove(key, index);
  339. this._updatePane(key);
  340. } else if (evt.target.classList.contains("btn-mark")) {
  341. evt.preventDefault();
  342. evt.stopPropagation();
  343. const row = evt.target.parentNode.parentNode.getAttribute("data-row");
  344. LinkStore.add("swagger-toolkit-mark", JSON.parse(row), true);
  345. this._updatePane("swagger-toolkit-mark");
  346. }
  347. });
  348. }
  349. _getRowIndex({ btnItem }) {
  350. const listDom = Array.from(
  351. btnItem.parentNode.parentNode.parentNode.children
  352. );
  353. for (let index = listDom.length; index--; ) {
  354. if (listDom[index] === btnItem.parentNode.parentNode) return index - 1;
  355. }
  356. return -1;
  357. }
  358. }
  359. Sheets.inject();
  360. SideBar.panes.push(new HistoryPane());
  361. SideBar.panes.push(new MarkPane());
  362.  
  363. const MAX_NUM = 30;
  364.  
  365. window.onload = setTimeout(() => {
  366. for (let i = 0; i < MAX_NUM; i++) {
  367. if (!document.querySelector(".opblock-tag")) {
  368. continue;
  369. }
  370.  
  371. const notOpenTagsList =
  372. document.querySelectorAll(".opblock-tag[data-is-open=false]") || [];
  373. for (const tag of Array.from(notOpenTagsList)) {
  374. tag.click();
  375. }
  376.  
  377. const wrapper = document.querySelector(".swagger-ui");
  378. wrapper.addEventListener("click", (evt) => {
  379. // 点击接口标题时在当前 URL 中加入锚点
  380. const linkTitleDom = evt.target.closest(".opblock-summary");
  381. if (linkTitleDom) {
  382. const linkDom = linkTitleDom.parentNode;
  383. const isOpen = !linkDom.classList.contains("is-open");
  384. const hash = isOpen ? linkDom.id : "";
  385. if (hash) location.hash = hash;
  386. return;
  387. }
  388. });
  389.  
  390. window.$$_SideBar = new SideBar();
  391. window.$$_SideBar
  392. .initCopyDOM()
  393. .addListeners()
  394. .generateDom()
  395. .appendPanes()
  396. .inject()
  397. .appendPanesListeners();
  398.  
  399. break;
  400. }
  401. }, 1000);