Easy Swagger

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);