Easy Swagger

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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