// ==UserScript==
// @name Easy Swagger
// @namespace https://github.com/peihaojie/Greasemonkey-script
// @description 注意:此脚本为自用包,请搜索 swagger-toolkit 安装原作者的脚本
// @description 注意: 需要增加适配网站,请手动修改 @include
// @include https://test-dms.skyallhere.com/api/swagger-ui/index.html
// @version 1.0
// @icon https://raw.githubusercontent.com/peihaojie/Greasemonkey-script/master/icon.png
// ==/UserScript==
class Sheets {
static sheets = `
body {
--row-width: 13vw;
--row-min-width: 245px;
--row-title-font-size: 14px;
--body-wrapper-width: 80vw;
--body-wrapper-margin-right: 3vw;
--body-wrapper-min-width: 800px;
--body-btn-group-width: 20px;
}
/* 应用于 Copy input */
.toolkit-hidden { width: 1; height: 1; }
/* 接口信息部分样式 */
#swagger-ui .opblock .toolkit-path-btn-group { margin-left: 10px; display: none; }
#swagger-ui .opblock:hover .toolkit-path-btn-group { display: block; }
#swagger-ui .opblock .toolkit-path-btn-group a { text-decoration: none; }
/* 页面内容主体布局 */
#swagger-ui div.topbar { display: flex; justify-content: flex-end; }
#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) }
#swagger-ui div.swagger-ui { display: flex; justify-content: flex-end; }
#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) }
/* sidebar part */
#swagger-toolkit-sidebar {
width: var(--row-width);
min-width: var(--row-min-width);
display: flex;
position: fixed;
top: 0;
left: 0;
height: 100vh;
flex-direction: column;
justify-content: space-between;
background-color: #FAFAFA;
border-right: 1px solid #c4d6d6;
}
#swagger-toolkit-sidebar .list { width: 100%; }
#swagger-toolkit-sidebar .list > header { font-size: 18px; background-color: #999; }
#swagger-toolkit-sidebar .list > header > .title { color: #FFF; text-align: center; font-weight: 200; }
#swagger-toolkit-sidebar .row { display: flex; padding-bottom: 5px; width: 100%; cursor: pointer; text-decoration: none; }
#swagger-toolkit-sidebar .row.method-DELETE { background-color: rgba(249,62,62,.1); }
#swagger-toolkit-sidebar .row.method-DELETE:hover { background-color: rgba(249,62,62,.5); }
#swagger-toolkit-sidebar .row.method-GET { background-color: rgba(97,175,254,.1); }
#swagger-toolkit-sidebar .row.method-GET:hover { background-color: rgba(97,175,254,.5); }
#swagger-toolkit-sidebar .row.method-POST { background-color: rgba(73,204,144,.1); }
#swagger-toolkit-sidebar .row.method-POST:hover { background-color: rgba(73,204,144,.5); }
#swagger-toolkit-sidebar .row.method-PUT { background-color: rgba(252,161,48,.1); }
#swagger-toolkit-sidebar .row.method-PUT:hover { background-color: rgba(252,161,48,.5); }
#swagger-toolkit-sidebar .row.method-PATCH { background-color: rgba(80,227,194,.1); }
#swagger-toolkit-sidebar .row.method-PATCH:hover { background-color: rgba(80,227,194,.5); }
#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)); }
#swagger-toolkit-sidebar .row .method { display: flex; line-height: 45px; min-width: 64px; }
#swagger-toolkit-sidebar .row .path > a { color: #409EFF; }
#swagger-toolkit-sidebar .row .btn-group { font-size: 12px; }
#swagger-toolkit-sidebar .row .btn-group > a { text-decoration: none; display: block; }
#swagger-toolkit-sidebar .row .btn-group > a:hover { font-size: 14px; }
/* helper */
.tool-text-size-fixed { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
`;
static inject() {
const sheet = document.createTextNode(Sheets.sheets);
const el = document.createElement("style");
el.id = "swagger-toolkit-sheets";
el.appendChild(sheet);
document.getElementsByTagName("head")[0].appendChild(el);
}
}
class LinkStore {
key = "";
path = "";
method = "";
description = ""; // 接口名
id = "";
createdat = 0;
static MAX_LENGTH = 10;
static save(row, key) {
const store = new LinkStore();
store.id = row.id;
store.key = key;
store.method = row.querySelector(".opblock-summary-method").innerText;
store.path = row.querySelector(".opblock-summary-path > a").innerText;
store.description = row.querySelector(
".opblock-summary-description"
).innerText;
LinkStore.add(key, store);
}
static add(key, store, filterRepeat) {
let data = LinkStore.getStore(key);
if (filterRepeat) {
for (const row of data) {
if (row.id === store.id && store.path === store.path) return false;
}
}
data.unshift(store);
if (data.length > LinkStore.MAX_LENGTH)
data = data.slice(0, LinkStore.MAX_LENGTH);
localStorage.setItem(key, JSON.stringify(data));
}
static remove(key, index) {
let data = LinkStore.getStore(key);
data.splice(index, 1);
localStorage.setItem(key, JSON.stringify(data));
}
static getStore(key) {
let store = [];
try {
const _store = localStorage.getItem(key);
if (_store) store = JSON.parse(_store);
} catch (err) {
console.error(err);
}
return store;
}
}
class Pane {
dom = null;
localKey = null;
title = null;
placeholder = "暂无数据";
placeholder_en = "no data";
btnSave = "收藏";
btnSave_en = "add to favorites";
btnRemove = "删除";
btnRemove_en = "remove";
enableMarkBtn = false;
/**
* 生成或更新当前 Pane
* @description 将生成 `.list>(header>.title)+(a.row>(.method+.contents>(.description+a.path)))`
*/
generateDom(isUpdate) {
if (isUpdate) this.dom.innerHTML = "";
const list = isUpdate ? this.dom : document.createElement("div");
list.classList.add("list");
list.classList.add(this.localKey);
list.setAttribute("data-key", this.localKey);
// 添加 header
const header = document.createElement("header");
const title = document.createElement("div");
title.classList.add("title");
title.innerText = this.getLabelByLanguage("title");
list.appendChild(header);
header.appendChild(title);
// 添加数据
const data = LinkStore.getStore(this.localKey);
for (const dataRow of data) {
const row = document.createElement("a");
row.href = "#" + dataRow.id;
row.setAttribute("data-row", JSON.stringify(dataRow));
const method = document.createElement("div");
method.innerText = dataRow.method;
const contents = document.createElement("div");
const description = document.createElement("div");
description.innerText = dataRow.description;
const path = document.createElement("div");
const pathLink = document.createElement("a");
pathLink.innerText = dataRow.path;
pathLink.href = "#" + dataRow.id;
const btnGroup = document.createElement("div");
const markBtn = document.createElement("a");
if (this.enableMarkBtn) {
markBtn.href = "javascript:;";
markBtn.setAttribute("title", this.getLabelByLanguage("btnSave"));
markBtn.innerText = "⭐️";
}
const deleteBtn = document.createElement("a");
deleteBtn.href = "javascript:;";
deleteBtn.setAttribute("title", this.getLabelByLanguage("btnRemove"));
deleteBtn.innerText = "✖️";
row.classList.add("row");
row.classList.add("method-" + dataRow.method);
method.classList.add("method");
contents.classList.add("contents");
description.classList.add("description");
description.classList.add("tool-text-size-fixed");
path.classList.add("path");
btnGroup.classList.add("btn-group");
if (this.enableMarkBtn) markBtn.classList.add("btn-mark");
deleteBtn.classList.add("btn-delete");
path.appendChild(pathLink);
contents.appendChild(description);
contents.appendChild(path);
// row.appendChild(method)
row.appendChild(contents);
row.appendChild(btnGroup);
btnGroup.appendChild(deleteBtn);
if (this.enableMarkBtn) btnGroup.appendChild(markBtn);
list.appendChild(row);
}
if (data.length === 0) list.appendChild(this.getPlaceholderDom());
this.dom = list;
if (typeof this.afterGenerageDom === "function") this.afterGenerageDom();
return list;
}
getPlaceholderDom() {
const dom = document.createElement("section");
dom.innerText = this.getLabelByLanguage("placeholder");
return dom;
}
getLabelByLanguage(field, language) {
let lang = language;
if (!lang) {
const _lang = navigator.language;
lang = _lang.indexOf("zh") === 0 ? "" : "en";
}
return this[`${field}${lang ? "_" + lang : ""}`];
}
}
class HistoryPane extends Pane {
localKey = "swagger-toolkit-history";
title = "浏览历史";
title_en = "History";
placeholder = "暂无浏览历史数据";
placeholder_en = "No history at present";
enableMarkBtn = true;
}
class MarkPane extends Pane {
localKey = "swagger-toolkit-mark";
title = "收藏夹";
title_en = "Favorites";
placeholder = "暂无收藏数据, 点击 ⭐️ 按钮添加";
placeholder_en = "No favorite data, click ⭐️ button to add";
afterGenerageDom() {
this.dom;
}
}
class SideBar {
static dom = null;
static panes = [];
static pathBtnGroupClassName = "toolkit-path-btn-group";
static copyInput = document.createElement("input");
initCopyDOM() {
SideBar.copyInput.classList.add("toolkit-hidden");
document.body.appendChild(SideBar.copyInput);
return this;
}
addListeners() {
window.addEventListener("hashchange", () => {
let _path = location.hash.length > 0 ? location.hash.substr(1) : "";
if (!_path) return;
_path = (window.decodeURI && window.decodeURI(_path)) || _path;
const row =
document.getElementById(_path) ||
(document.querySelector(`a[href="#${_path}"]`) &&
document.querySelector(`a[href="#${_path}"]`).closest(".opblock"));
if (row) LinkStore.save(row, "swagger-toolkit-history");
this._updatePane("swagger-toolkit-history");
});
document
.querySelector("#swagger-ui")
.addEventListener("mouseover", (evt) => {
this._showPathBtnGroup(evt); // 显示在 path 栏中的按钮组
});
return this;
}
_showPathBtnGroup(evt) {
const opblock = evt.target.closest(".opblock");
if (!opblock) return;
this._appendPathBtnGroupDOM(opblock);
}
_appendPathBtnGroupDOM(opblock) {
if (opblock.querySelector("." + SideBar.pathBtnGroupClassName)) return;
const group = document.createElement("div");
const copyBtn = document.createElement("a");
group.classList.add(SideBar.pathBtnGroupClassName);
copyBtn.setAttribute("href", "javascript:;");
copyBtn.classList.add("btn-copy");
copyBtn.innerText = "🔗";
copyBtn.setAttribute("title", "copy");
group.appendChild(copyBtn);
copyBtn.addEventListener("click", (evt) => {
this._copyPath(evt);
});
const pathDOM = opblock.querySelector(".opblock-summary-path");
if (pathDOM) pathDOM.appendChild(group);
}
_copyPath(evt) {
evt.stopPropagation();
const pathDOM = evt.target.closest(".opblock-summary-path");
if (!pathDOM) return;
const pathLink = pathDOM.querySelector("a");
if (!pathLink) return;
const path = pathLink.innerText;
SideBar.copyInput.value = path;
SideBar.copyInput.select();
document.execCommand("Copy");
console.log("copy successfuly");
}
generateDom() {
const sidebar = document.createElement("sidebar");
sidebar.id = "swagger-toolkit-sidebar";
SideBar.dom = sidebar;
return this;
}
inject() {
document.body.appendChild(SideBar.dom);
return this;
}
appendPanes() {
for (const pane of SideBar.panes) {
SideBar.dom.appendChild(pane.generateDom());
}
return this;
}
_updatePane(key) {
for (const pane of SideBar.panes) {
if (pane.localKey !== key) continue;
pane.generateDom(true);
}
}
appendPanesListeners() {
SideBar.dom.addEventListener("click", (evt) => {
if (evt.target.classList.contains("btn-delete")) {
evt.preventDefault();
evt.stopPropagation();
const index = this._getRowIndex({ btnItem: evt.target });
const key =
evt.target.parentNode.parentNode.parentNode.getAttribute("data-key");
LinkStore.remove(key, index);
this._updatePane(key);
} else if (evt.target.classList.contains("btn-mark")) {
evt.preventDefault();
evt.stopPropagation();
const row = evt.target.parentNode.parentNode.getAttribute("data-row");
LinkStore.add("swagger-toolkit-mark", JSON.parse(row), true);
this._updatePane("swagger-toolkit-mark");
}
});
}
_getRowIndex({ btnItem }) {
const listDom = Array.from(
btnItem.parentNode.parentNode.parentNode.children
);
for (let index = listDom.length; index--; ) {
if (listDom[index] === btnItem.parentNode.parentNode) return index - 1;
}
return -1;
}
}
Sheets.inject();
SideBar.panes.push(new HistoryPane());
SideBar.panes.push(new MarkPane());
const MAX_NUM = 30;
window.onload = setTimeout(() => {
for (let i = 0; i < MAX_NUM; i++) {
if (!document.querySelector(".opblock-tag")) {
continue;
}
const notOpenTagsList =
document.querySelectorAll(".opblock-tag[data-is-open=false]") || [];
for (const tag of Array.from(notOpenTagsList)) {
tag.click();
}
const wrapper = document.querySelector(".swagger-ui");
wrapper.addEventListener("click", (evt) => {
// 点击接口标题时在当前 URL 中加入锚点
const linkTitleDom = evt.target.closest(".opblock-summary");
if (linkTitleDom) {
const linkDom = linkTitleDom.parentNode;
const isOpen = !linkDom.classList.contains("is-open");
const hash = isOpen ? linkDom.id : "";
if (hash) location.hash = hash;
return;
}
});
window.$$_SideBar = new SideBar();
window.$$_SideBar
.initCopyDOM()
.addListeners()
.generateDom()
.appendPanes()
.inject()
.appendPanesListeners();
break;
}
}, 1000);