apollo-enhance

make old apollo better

目前為 2022-11-20 提交的版本,檢視 最新版本

// ==UserScript==
// @name         apollo-enhance
// @namespace    apollo-enhance
// @version      0.8.5
// @description  make old apollo better
// @homepage     https://github.com/xyz327/old-apollo-portal-enhance
// @website      https://github.com/xyz327/old-apollo-portal-enhance
// @source       https://github.com/xyz327/old-apollo-portal-enhance
// @author       xizhou
// @match        *://*/config.html*
// @resource     highlight_xcode_css https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/highlight.js/9.18.5/styles/xcode.min.css
// @require      https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/diff_match_patch/20121119/diff_match_patch_uncompressed.js
// @require      https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/highlight.js/9.18.5/highlight.min.js
// @require      https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/highlight.js/9.18.5/languages/json.min.js
// @resource     text_different_css https://cdn.jsdelivr.net/npm/[email protected]/build/style/text-different.min.css
// @require      https://cdn.jsdelivr.net/combine/npm/[email protected]/build/text-different.min.js,npm/[email protected]/build/text-different-for-html.min.js
// @noframes
// @grant      GM_getResourceText
// @grant      GM_addStyle
// @grant      GM_addElement
// ==/UserScript==

(function () {
  'use strict';

  var enhanceNavId = "apollo-enhance-nav";
  var featureId = "apollo-enhance-feature";
  function appendNavBar(child) {
    $(`#${enhanceNavId}`).append(child);
  }
  loadFeature("nav", false, function () {
    var $navBar = $("#bs-example-navbar-collapse-1");
    $navBar.append(`
        <ul id="${enhanceNavId}" class="nav navbar-nav navbar-right">
          
        </ul>
        `);
    return true;
  });
  (function () {
    initFeatureId();
    initDiffModal();
    $("[data-copy]").on("click", function (e) {
      copy($(e.currentTarget).attr("data-copy-value")).then(function () {
        var $icon = $(e.target).parent().find(".glyphicon");
        $icon.removeClass("glyphicon-duplicate").addClass("glyphicon-ok");
        setTimeout(function () {
          $icon.addClass("glyphicon-duplicate").removeClass("glyphicon-ok");
        }, 2000);
      });
    });
    // 加载 layer  依赖 $
    GM_addElement('script', {
      src: 'https://cdn.jsdelivr.net/npm/[email protected]/src/layer.js',
      type: 'text/javascript'
    });
  })();
  loadFeature("onNamesacpeLoaded", true, function () {
    var $namespaces = $(".namespace-name");
    if ($namespaces.length == 0) {
      return false;
    }
    console.log('trigger namespaceLoaded');
    $("body").trigger("namespaceLoaded");
  });
  function getAppId() {
    let hash = location.hash;
    if (hash) {
      hash = hash.substring(2);
      const url = new URL("http://localhost?" + hash);
      return url.searchParams.get("appid");
    }
  }

  function loadFeature(name, reloadOnHashChange, feature) {
    loadFeature0(name, feature, false);
    if (reloadOnHashChange) {
      $(window).on("hashchange", function (e) {
        loadFeature0(name, feature, true);
      });
    }
  }
  function showDiffModal(key, newVal, oldVal) {
    const tdfh = new TextDifferentForHtml(
      $("#diff-container")[0], // The dom used to render the display code
      "json" // Type of code
    );
    $("#diff-detail-title").html(`${key}`);
    $("#copyOld").attr("data-copy-value", oldVal);
    $("#copyNew").attr("data-copy-value", newVal);
    tdfh.render({
      oldCode: toPerttyJson(oldVal), // Old code
      newCode: toPerttyJson(newVal), // New code
      hasLineNumber: false, // Whether to display the line number
    });
    $("#diffModal").modal();
  }

  function copy(content) {
    return new Promise(function (res, rej) {
      let copy = function (e) {
        try {
          e.preventDefault();
          e.clipboardData.setData("text/plain", content);
          document.removeEventListener("copy", copy);
          console.log("copy value:", content);
          res();
        } catch (e) {
          rej();
        }
      };
      document.addEventListener("copy", copy);
      document.execCommand("Copy");
    });
  }
  function toPerttyJson(val) {
    try {
      return JSON.stringify(JSON.parse(val), null, 2);
    } catch (e) {
      return val;
    }
  }

  function loadFeature0(name, feature, isReloadByHash) {
    try {
      if ($("#feature-" + name).length !== 0) {
        // 已经加载过了
        console.log(`loadFeature: ${name} has loaded`);
        return;
      }
      var clear = setInterval(function () {
        if (feature(isReloadByHash) !== false) {
          console.log(`loadFeature: ${name} finished`);
          $(`${"#" + featureId}`).append(`<div id="feature-${name}"></div>`);
          clearInterval(clear);
        }
      }, 1000);
    } catch (e) {
      console.error(`load feature failed :${name}`, e.message);
    }
  }

  function initFeatureId() {
    $("body").prepend(`<div id="${featureId}" class="hidden"></div>`);
  }

  function initDiffModal() {
    $("body").append(`
      <!-- Modal -->
      <div class="modal fade" id="diffModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
        <div class="modal-dialog modal-lg" role="document">
          <div class="modal-content">
            <div class="modal-header">
              <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
              <h4 class="modal-title"><span class="text-danger" id="diff-detail-title"></span> 差异对比</h4>
            </div>
            <div class="modal-body" >
                <div class="row">
                  <div class="col-xs-6 text-center">
                    <span data-tooltip="tooltip" title="点击复制" id="copyOld" data-copy="copy" data-copy-value=""  class="label label-default">旧值 
                    <label class="glyphicon glyphicon-duplicate"></label>
                    </span>
                  </div>
                  <div class="col-xs-6 text-center">
                    <span data-tooltip="tooltip" title="点击复制" id="copyNew" data-copy="copy" data-copy-value="" class="label label-success">新值
                    <label class="glyphicon glyphicon-duplicate"></label>
                    </span>
                  </div>
                </div>
                <div id="diff-container" style="display:flex"></div>
            </div>
          </div>
        </div>
      </div>
      `);
  }

  loadFeature("fixNiceScroll", false, function () {
      $(document).ready(function () {
        // 放在初始化之后执行
        setTimeout(function () {
          $("html").css("overflow", "");
        }, 200);
      });
      return true;
    });

  loadFeature("fixEnvTab", true, function (isReloadByHash) {
      var $tab = $(".J_appFound");
      if ($tab.length == 0) {
        return false;
      }
      const infoVal = sessionStorage[getAppId()];
      const infoObj = JSON.parse(infoVal);
      const cluster = infoObj.cluster;
      const env = infoObj.env;
      var $panelHeader = $tab.find(".panel-heading:first");
      var $curEnvInfo = $panelHeader.find("#curEnvInfo");
      if ($curEnvInfo.length == 0) {
        $tab.find(".panel-heading>span").after(`
          <button type="button" class="slideBtn btn btn-primary btn-xs">(点击展开/收缩)</button>
      `);
        $panelHeader.append(`<div id="curEnvInfo"></div>`);
        $curEnvInfo = $panelHeader.find("#curEnvInfo");
      }
      $curEnvInfo.html(`
    <span class="label label-success">${env}</span> - <span class="label label-info">${cluster}</span>
    `);
      if (!isReloadByHash) {
        // 不是通过 hash change reload 的才需要绑定事件
        $tab.find(".panel-heading .slideBtn").on("click", function (e) {
          const $header = $(e.target).parent(".panel-heading");
          $header.next("div").slideToggle("normal", function () {});
        });

        $tab = $tab.parent();
        $tab.on("affixed.bs.affix", function (e) {
          $tab.css({ position: "fixed" });
          $tab
            .find(".panel-heading")
            .next("div")
            .slideUp("normal", function () {});
        });
        $tab.on("affixed-top.bs.affix	", function (e) {
          $tab.css({ position: "" });
          $tab
            .find(".panel-heading")
            .next("div")
            .slideDown("normal", function () {});
        });
        $tab.affix({
          offset: {
            top: 50,
          },
        });
      }
      return true;
    });

  loadFeature("disableScrollOnModal", false, function () {
      $("body")
        .on("show.bs.modal", function () {
          $("html").css("overflow", "hidden");
        })
        .on("hide.bs.modal", function () {
          $("html").css("overflow", "");
        });
      return true;
    });

  loadFeature("gotoNamespace", true, () => {
    var $namespaces = $(".namespace-name");
    if ($namespaces.length == 0) {
      return false;
    }
    goToNamespace0();
    return true;
  });

  function goToNamespace0() {
    if ($("#affixPlaceholder").length == 0) {
      $("body>nav.navbar").width("100%").css({ "z-index": 999 }).affix({
        top: 0,
      });
      $("body>nav.navbar").after('<div id="affixPlaceholder"></div>');
      var $affixPlaceholder = $("#affixPlaceholder");
      $("body>nav.navbar").on("affix.bs.affix", function (event) {
        $affixPlaceholder.css("height", "50px");
      });
      $("body>nav.navbar").on("affix-top.bs.affix", function (event) {
        $affixPlaceholder.css("height", "0px");
      });
    }
    const observer = new MutationObserver(() => {
      console.log('rebuild gotoNamesapce');
      // 重新加载 namespace selector 的修改状态
      buildGotoNamespace();
    });
    $.each($(".config-item-container"), (index, el) => {
      observer.observe(el, { childList: true });
    });
    buildGotoNamespace();
  }

  function buildGotoNamespace() {
    $("#goToNamespace").remove();
    var $namespaces = $(".namespace-name");
    $namespaces.attr("data-namespace", function(){
      return this.innerHTML.replace('.', "-")
    });
    var list = "";
    var namespaceOffsets = [];
    var lastNamespaceId = "application";
    for (const namespace of $namespaces) {
      var $namespace = $(namespace);
      var namespaceVal = $namespace.text();
      var namespaceId = namespaceVal; //$namespace.text().replaceAll(".", "-");
      namespaceOffsets.push({
        top: $namespace.offset().top,
        id: lastNamespaceId,
      });
      lastNamespaceId = namespaceId;
      var changed =
        $(namespace).parent().parent().find(".modify-tip.ng-hide").length === 0;
      //$namespace.parents('header.panel-heading').after(`<a href="#${namespaceId}" id="${namespaceId}"></a>`);
      list += `<option value="${namespaceId}" ${changed?'data-change="1"':''}>${namespaceVal}</option>`;
    }

    appendNavBar(`
          <li id="goToNamespace" style="margin-top: 10px;">
          <select id="namespaceSelecter">${list}</select>
          </li>
          `);
    var $select = $("#namespaceSelecter");
    // init changed

    $select.select2({
      placeholder: "跳转到 Namespace",
      templateResult: function (state) {
        var changed = $(state.element).attr("data-change");
        if (changed) {
          return $(
            `<label>${state.text} <span class="label label-warning ">改</span></label>`
          );
        }
        return `${state.text}`;
      },
    });
    $select.on("select2:open", function (e) {
      $("#select2-namespaceSelecter-results").css({ "max-height": "600px" });
    });
    var selectedVal;
    var triggerBySelect = false;
    var htmlScroll = $("html").getNiceScroll(0);
    htmlScroll.scrollend(function (e) {
      if (triggerBySelect) {
        triggerBySelect = false;
        return;
      }
      var offsetY = e.end.y;
      var curNamespace = namespaceOffsets.find((val) => val.top > offsetY);
      if (curNamespace && selectedVal != curNamespace.id) {
        //TODO
        selectedVal = curNamespace.id;
        $select.val(selectedVal).trigger("change");
      }
    });
    $select.on("select2:select", function (e) {
      var namespaceId = $select.val();

      var namespaceEl = $(".namespace-name")
        .toArray()
        .find((el) => el.innerHTML == namespaceId);
      triggerBySelect = true;
      htmlScroll.doScrollTop($(namespaceEl).offset().top - 100, 1000);
    });
  }

  var DiffMatch = new diff_match_patch();

  loadFeature("releaseDiff", false, function () {
    var releaseModalNode = document.querySelector("#releaseModal");
    if (releaseModalNode == null) {
      return false;
    }
    bindDiffInfo(releaseModalNode);
    return true;
  });

  function bindDiffInfo(node) {
    initDiifLib();
    var observer = new MutationObserver(function () {
      initChangeInfoHeader();
      // 每次都需要隐藏
      initChangeInfoDetail();
      var $cols = $("#releaseModal table tr.ng-scope");

      var kvInfo = {};
      for (const col of $cols) {
        var $col = $(col);
        var tds = $(col).find("td");
        kvInfo = {
          key: tds[0].title,
          oldVal: tds[1].title,
          newVal: tds[2].title,
        };

        buildDiffHtml(
          $col.find("td.diff-text"),
          kvInfo.key,
          kvInfo.oldVal,
          kvInfo.newVal
        );
      }
    });
    observer.observe(node, {
      attributeFilter: ["style"],
    });
  }

  function toggleDiff() {
    $(".change-diff").toggle();
    var needShow = $(".change-diff").is(":hidden");
    if (needShow) {
      $(".change-detail").show();
    } else {
      $(".change-detail").hide();
    }
  }
  function initChangeInfoDetail() {
    $(".change-detail").hide();
    var $cols = $("#releaseModal table tr.ng-scope");
    for (var col of $cols) {
      var $col = $(col);
      if ($col.hasClass("diff-info-inited")) {
        return;
      }
      initChageCol();
      $col.addClass("diff-info-inited");
    }
  }
  function initChageCol() {
    var bodyRows = $("#releaseModal table tr.ng-scope");
    for (var row of bodyRows) {
      var $row = $(row);
      if ($row.find("td.change-diff").length == 0) {
        $row.find("td:gt(0)").addClass("change-detail x-detail").hide();
        $row.append(
          '<td class="change-diff diff-text" data-toggle="tooltip" data-placement="top" title="点击查看详细差异对比"></td>'
        );
      }
    }
    $(".change-diff.diff-text").tooltip();
  }
  function initChangeInfoHeader() {
    if ($("#releaseModal table thead tr>th").length == 0) {
      return;
    }
    if ($("#toggleDiff").length != 0) {
      return;
    }
    // 隐藏原有信息
    $("#releaseModal table thead tr>th:gt(0)").addClass("change-detail").hide();
    // 增加差异信息展示
    var headCol = $("#releaseModal table thead tr");
    headCol.append('<th class="change-diff">差异(点击查看新旧值对比)</th>');
    $("#releaseModal table thead tr>th:eq(0)").append(
      '<button id="toggleDiff">切换显示</button>'
    );
    $("#toggleDiff").click(function () {
      toggleDiff();
      return false;
    });
  }

  function initDiifLib() {
    const highlight_xcode_css = GM_getResourceText("highlight_xcode_css");
    const text_different_css = GM_getResourceText("text_different_css");
    GM_addStyle(highlight_xcode_css);
    GM_addStyle(text_different_css);
  }
  function buildDiffHtml($node, key, oldVal, newVal) {
    // 新增或删除

    var diff = DiffMatch.diff_main(oldVal, newVal);

    DiffMatch.diff_cleanupSemantic(diff);

    var html = DiffMatch.diff_prettyHtml(diff);
    $node.html(html);
    var errorJson = isErrorJson(newVal);
    if (errorJson) {
      var $td = $node.parent().find("td:first");
      var errorJsonLabelId = `${key}-errorJson`;
      if ($(`#${errorJsonLabelId}`).length == 0) {
        $td.append(
          `<span id="${errorJsonLabelId}" class="label label-danger">错误的json</span>`
        );
        $td.addClass("alert alert-danger");
      }
    }
    $node.on("click", function () {
      showDiffModal(key, newVal, oldVal);
    });
  }

  function isErrorJson(val) {
    val = val.trim();
    if (val.startsWith("{") || val.startsWith("[")) {
      try {
        JSON.parse(val);
        return false;
      } catch (e) {
        return true;
      }
    }
    return false;
  }

  loadFeature("releaseModal", true, function () {
      $('#releaseModal div.modal-header .modal-title:not(".ng-hide")')
      .append(`<span id="goReleaseMoadlBottom" class="glyphicon glyphicon-circle-arrow-down" data-tooltip="tooltip" data-placement="top" title="定位到发布按钮"></span>`);

      $('#releaseModal div.modal-footer')
      .prepend(`
    <span id="goReleaseMoadlTop" class="pull-left glyphicon glyphicon-circle-arrow-up" data-tooltip="tooltip" data-placement="top" title="回到顶部"></span>`);

      // for scroll
      var nicesocre = $('#releaseModal').niceScroll({cursoropacitymax: 0});
      $('#goReleaseMoadlBottom').on('click',function(){
          nicesocre.doScrollTop($('#goReleaseMoadlTop').offset().top, 1000);
      });
     
      $('#goReleaseMoadlTop').on('click',function(){
          nicesocre.doScrollTop($('#goReleaseMoadlBottom').offset().top, 1000);
      });
      return true;
  });

  loadFeature("showText", true, function () {
    var $namespaces = $(".namespace-view-table");
    if ($namespaces.length == 0) {
      return false;
    }
    var currItem;
    $("#showTextModal .modal-body")
      .tooltip({
        title: "点击查看差异对比",
      })
      .on("click", function () {
        if (!currItem) {
          return;
        }
        if (currItem.isModified) {
          console.log(currItem);
          showDiffModal(currItem.item.key, currItem.newValue, currItem.oldValue);
        }
      });
    $(".namespace-view-table div.ng-scope:not(.no-config-panel)")
      .filter(function (idx) {
        currItem = null;
        var $el = $(this);
        if ($el.hasClass("panel")) {
          // 关联 namespace 的覆盖配置
          return true;
        }
        var ngIf = $el.attr("ng-if");
        if (ngIf === "!namespace.isLinkedNamespace") {
          // 私有 namespace 的配置
          return true;
        }
        // 关联 namespace 的配置
        return false;
      })
      .find("tbody>tr")
      .find("td:eq(2)")
      .filter(".cursor-pointer")
      .on("click", function (e) {
        var $td = $(e.currentTarget);
        var key = $td.prev("td").find('span:eq(0)').text().trim();
        var namespace = $td
          .parents("section.master-panel-body.ng-scope")
          .find("b.namespace-name.ng-binding")
          .text()
          .trim();
        var namespaceScope = $(
          'div[ng-controller="ConfigNamespaceController"]'
        ).scope();
        var namesapce = namespaceScope.namespaces.find(
          (e) => e.baseInfo.namespaceName === namespace
        );
        currItem = namesapce.items.find((e) => e.item.key === key);
      });
  });

  loadFeature("help", false, function () {
      helpInfo();
      return true;
    });

  function helpInfo() {
      initFeatureInfoModal();
      appendNavBar(`
    <li>
    <a href="javascript:void(0);" id="showFeatureInfo">
    <span class="glyphicon glyphicon-question-sign"></span>
    </a>
    </li>
    `);
      $("#showFeatureInfo").on("click", showFeatureInfo);
    }

    function showFeatureInfo() {
      var data = [
        {
          title: "json格式校验",
          img: "https://raw.githubusercontent.com/xyz327/old-apollo-portal-enhance/main/doc/change-diff-1.png",
        },
        {
          title: "namespace跳转",
          img: "https://raw.githubusercontent.com/xyz327/old-apollo-portal-enhance/main/doc/gotoNamespace.png",
        },
      ];
      var content = "";
      for (const val of data) {
        content += `
      <div class="panel panel-default">
        <div class="panel-heading">
          <h3 class="panel-title">${val.title}</h3>
        </div>
        <div class="panel-body">
          <img class="img-responsive" src="${val.img}"/>
        </div>
      </div>
      `;
      }
      $("#featureModalBody").html(content);
      $("#featureModal").modal();
    }
    function initFeatureInfoModal() {
      $("body").append(`
    <!-- Modal -->
    <div class="modal fade" id="featureModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
      <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
            <h4 class="modal-title">apollo-enhance 功能说明</h4>
          </div>
          <div class="modal-body" id="featureModalBody">
             
          </div>
        </div>
      </div>
    </div>
    `);
    }

})();