Easy Swagger

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

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