bitbucket-global-search

bitbucket全局搜索

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         bitbucket-global-search
// @version      0.0.8
// @author       alan
// @include      https://code.fineres.com/*
// @noframes
// @description  bitbucket全局搜索
// @namespace bitbucket-global-search
// @license MIT
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @resource css https://code.fineres.com/download/resources/com.atlassian.bitbucket.server.bitbucket-frontend:split_dashboard/dashboard.bundle.css
// ==/UserScript==
(() => {
  "use strict";
  var __webpack_exports__ = {};
  ;
  function getLastReps() {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: "https://code.fineres.com/rest/api/latest/profile/recent/repos?avatarSize=64&limit=10",
        headers: {
          Accept: "application/json, text/javascript, */*; q=0.01",
          "Accept-Language": "zh,zh-CN;q=0.9,en-US;q=0.8,en;q=0.7",
          "Cache-Control": "no-cache",
          Connection: "keep-alive",
          "Content-Type": "application/json",
          "X-Requested-With": "XMLHttpRequest",
          cookie: document.cookie
        },
        data: ``,
        onload(response) {
          const result = JSON.parse(response.responseText);
          resolve(result);
        },
        onerror(response) {
          console.error("\u8BF7\u6C42\u5931\u8D25:");
          console.error(response);
          reject(response);
        }
      });
    });
  }
  function getAllPorjects() {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: "https://code.fineres.com/rest/categories/latest/projects?start=0&limit=5000&project=",
        headers: {
          Accept: "application/json, text/javascript, */*; q=0.01",
          "Accept-Language": "zh,zh-CN;q=0.9,en-US;q=0.8,en;q=0.7",
          "Cache-Control": "no-cache",
          Connection: "keep-alive",
          "Content-Type": "application/json",
          "X-Requested-With": "XMLHttpRequest",
          cookie: document.cookie
        },
        data: ``,
        onload(response) {
          const result = JSON.parse(response.responseText);
          resolve(result);
        },
        onerror(response) {
          console.error("\u8BF7\u6C42\u5931\u8D25:");
          console.error(response);
          reject(response);
        }
      });
    });
  }
  function getReposByProject(project) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: `https://code.fineres.com/rest/api/latest/projects/${project}/repos?avatarSize=64&limit=5000`,
        headers: {
          Accept: "application/json, text/javascript, */*; q=0.01",
          "Accept-Language": "zh,zh-CN;q=0.9,en-US;q=0.8,en;q=0.7",
          "Cache-Control": "no-cache",
          Connection: "keep-alive",
          "Content-Type": "application/json",
          "X-Requested-With": "XMLHttpRequest",
          cookie: document.cookie
        },
        data: ``,
        onload(response) {
          const result = JSON.parse(response.responseText);
          resolve(result);
        },
        onerror(response) {
          console.error("\u8BF7\u6C42\u5931\u8D25:");
          console.error(response);
          reject(response);
        }
      });
    });
  }
  ;
  var __async = (__this, __arguments, generator) => {
    return new Promise((resolve, reject) => {
      var fulfilled = (value) => {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      };
      var rejected = (value) => {
        try {
          step(generator.throw(value));
        } catch (e) {
          reject(e);
        }
      };
      var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
      step((generator = generator.apply(__this, __arguments)).next());
    });
  };
  let cacheResult = [];
  function appendEntryButton() {
    const nav = document.querySelector(".aui-nav");
    const a = document.createElement("a");
    a.innerText = "\u5168\u5C40\u641C\u7D22";
    a.style.cursor = "pointer";
    const li = document.createElement("li");
    li.appendChild(a);
    nav == null ? void 0 : nav.appendChild(li);
    return a;
  }
  function init() {
    appendDialog();
  }
  function appendDialog() {
    const dialog = document.createElement("div");
    dialog.innerHTML = `
    <div class="aui-dialog2 aui-dialog2-large aui-dialog2-current aui-layer" id="aui-dialog2-1" role="dialog" aria-labelledby="aui-dialog2-1-heading" aria-hidden="true">
      <div class="aui-dialog2-header">
        <h2 class="aui-dialog2-header-main" id="aui-dialog2-1-heading">\u5168\u5C40\u641C\u7D22</h2>
        <a class="aui-dialog2-header-close">
          <span class="aui-icon aui-icon-small aui-iconfont-close-dialog">Close</span>
        </a>
      </div>
      <div class="aui-dialog2-content" style="height: 500px;">
        <div class="aui-field-group">
          <div class="aui-field">
            <form class="aui" onsubmit="return false">
                <input placeholder="\u8BF7\u8F93\u5165\u5173\u952E\u5B57" id="search" class="text" type="search" name="search">
                <input type="button" value="\u5168\u5C40\u641C\u7D22" id="search-button" class="aui-button"></input>
            </form>
          </div>
        </div>
        <div class="search-result-repositories">
          <ol id="search-result-repositories" class="dashboard-repositories-list"></ol>
        </div>
      </div>
      <div class="aui-dialog2-footer">
        <div id="search-progress" style="display: inline-flex;align-items: center;height: 100%;">
        </div>
        <div class="aui-dialog2-footer-actions">
          <button class="aui-button" id="close-btn">\u5173\u95ED</button>
        </div>
      </div>
    </div>
  `;
    document.body.appendChild(dialog);
    AJS.$("#close-btn").click(() => {
      AJS.dialog2("#aui-dialog2-1").hide();
    });
    AJS.$(".aui-iconfont-close-dialog").click(() => {
      AJS.dialog2("#aui-dialog2-1").hide();
    });
    return dialog;
  }
  function renderSearchResult(items) {
    const repositories = document.querySelector("#search-result-repositories");
    if (repositories) {
      repositories;
      repositories.innerHTML = `${items.map((item) => `<li><a href="/projects/${item.project.key}/repos/${item.name}/browse" aria-label="${item.project.name} / ${item.name}"><div class="project-avatar"><div style="display: inline-block; position: relative; outline: 0px;"><span class="css-50fm9s"><span class="css-1gv8fjs" role="img" aria-label="" style="background-image: url('${item.project.avatarUrl}');height: 40px;background-size: contain;"></span></span></div></div><div class="repository-details"><div class="repository-name">${item.name}</div><div class="project-name">${item.project.name}</div></div></a></li>`).join("")}`;
    }
  }
  function renderLoading() {
    const repositories = document.querySelector("#search-result-repositories");
    if (repositories) {
      repositories.innerHTML = `<div class="loading"><div class="loading-spinner" style="height: 100px;width: 100%;display: flex;justify-content: center;align-items: center;"><aui-spinner></aui-spinner></div></div>`;
    }
  }
  function renderEmpty() {
    const repositories = document.querySelector("#search-result-repositories");
    if (repositories) {
      repositories.innerHTML = `<div style="height: 100px;width: 100%;display: flex;justify-content: center;align-items: center;" class="empty">\u6682\u65E0\u6570\u636E</div>`;
    }
  }
  function filterReps(keyWord, render) {
    return __async(this, null, function* () {
      let searchResult = cacheResult;
      if (searchResult.length === 0) {
        const result2 = yield getAllPorjects();
        const projects = result2.result;
        for (let index = 0; index < projects.length; index++) {
          const project = projects[index];
          AJS.$("#search-progress").text(`\u6B63\u5728\u641C\u7D22${project.projectName}\u7684\u4ED3\u5E93`);
          const repo = yield getReposByProject(project.projectKey);
          searchResult = [...searchResult, ...repo.values];
        }
        cacheResult = searchResult;
      }
      const result = searchResult.filter((v) => {
        return v.name.toLowerCase().includes(keyWord.toLowerCase());
      });
      if (result.length > 0) {
        render(result);
      } else {
        renderEmpty();
      }
    });
  }
  function debounce(fn, wait) {
    let timeout = null;
    return function() {
      if (timeout !== null)
        clearTimeout(timeout);
      timeout = window.setTimeout(fn, wait);
    };
  }
  function main() {
    return __async(this, null, function* () {
      appendStyle();
      cacheResult = JSON.parse(localStorage.getItem("bitbucket-search-result") || "[]");
      init();
      appendEntryButton().addEventListener("click", () => __async(this, null, function* () {
        var _a;
        const search = document.getElementById("search");
        search.value = "";
        search.focus();
        const inputAction = debounce(() => {
          if (cacheResult.length === 0)
            return;
          const { value } = search;
          if (value) {
            filterReps(value, renderSearchResult);
          } else {
            renderEmpty();
          }
        }, 500);
        search.addEventListener("input", inputAction);
        AJS.dialog2("#aui-dialog2-1").show();
        (_a = document.getElementById("search")) == null ? void 0 : _a.addEventListener("keydown", (e) => {
          if (e.keyCode === 13) {
            AJS.$("#search-button").click();
          }
        });
        AJS.$("#search-button").click(() => __async(this, null, function* () {
          cacheResult = [];
          const { values } = yield getLastReps();
          const searchText = search.value;
          renderLoading();
          if (!searchText) {
            renderSearchResult(values);
          }
          yield filterReps(searchText, renderSearchResult);
          AJS.$("#search-progress").text("");
          localStorage.setItem("bitbucket-search-result", JSON.stringify(cacheResult));
        }));
      }));
    });
  }
  function appendStyle() {
    GM_addStyle(GM_getResourceText("css"));
    GM_addStyle(`
    .css-50fm9s {
      height: 40px;
      width: 40px;
      -webkit-box-align: stretch;
      align-items: stretch;
      background-color: rgb(255, 255, 255);
      border-radius: 3px;
      box-sizing: content-box;
      cursor: inherit;
      display: flex;
      flex-direction: column;
      -webkit-box-pack: center;
      justify-content: center;
      outline: none;
      overflow: hidden;
      position: static;
      transform: translateZ(0px);
      transition: transform 200ms ease 0s, opacity 200ms ease 0s;
      box-shadow: rgb(255, 255, 255) 0px 0px 0px 2px;
      border: none;
      margin: 2px;
      padding: 0px;
      font-size: inherit;
      font-family: inherit;
    }
  `);
  }
  setTimeout(() => {
    main();
  }, 500);
})();