Easy Swagger

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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