iT邦幫忙

2025 iThome 鐵人賽

DAY 18
0
佛心分享-SideProject30

從零開始改善工作之 Chrome Extension: MR 通知文字小工具系列 第 18

Day 18:實作 ─ 支援多專案 Merge Request 的差異格式

  • 分享至 

  • xImage
  •  

昨天大致上已經知道要怎麼處理,今天就來逐步調整吧!

實作

  1. 改寫 background.js 儲存預設專案管理的格式,將 string 改為 object

    chrome.runtime.onInstalled.addListener(async () => {
         const {
             reviewTemplates
         } = await chrome.storage.sync.get(['reviewTemplates']);
    
         if (!reviewTemplates) {
             chrome.storage.sync.set({
                 reviewTemplates: {
                     default: defaultReviewTemplate
                 }
             });
         }
     });
    
  2. 因為必須判斷目前是載哪一個 url,所以要在 ontent_script.js 加上從 url 取得專案的相關程式

     function getProjectKey() {
         const url = window.location.href;
         const beforeDash = url.split('/-/')[0];
         const parts = beforeDash.split('/');
         return parts.pop() || 'default';
     }
    
  3. 改寫 content_script.js 將 chrome.storage.sync 取得的範本從原本的 string 改寫為 object,主要調整的部分為 generateReviewContent

    • 調整取得的參數名稱且取得 content 需要搭配專案名稱
     async function generateReviewContent(title, reviewer) {
         const {
             reviewTemplates
         } = await chrome.storage.sync.get(['reviewTemplates']);
    
         const reviewerNameSymbol = "{REVIEWER_NAME}";
         const mrLinkSymbol = "{MR_LINK}";
    
         const textContent = (reviewTemplates[getProjectKey()] ?? reviewTemplates.default)
             .replace(reviewerNameSymbol, reviewer)
             .replace(mrLinkSymbol, title)
    
         // 準備 HTML 內容(包含格式化連結)
         const mrUrl = window.location.href;
         const htmlContent = (reviewTemplates[getProjectKey()] ?? reviewTemplates.default)
             .replace(reviewerNameSymbol, reviewer)
             .replace(mrLinkSymbol, `<a href="${mrUrl}">${title}</a>`)
         console.log(htmlContent);
    
         // 使用 ClipboardItem 建立可複製的內容,使用 .replace 方法替換要複製的內容
         const clipboardItem = new ClipboardItem({
             "text/plain": new Blob(
                 [
                     textContent
                 ],
                 { type: "text/plain" }
             ),
             "text/html": new Blob(
                 [
                     htmlContent
                 ],
                 { type: "text/html" }
             ),
         });
    
         return clipboardItem
     }
    
  4. 因為 Content Script 和 popup 頁面沒有直接相連,要透過 message passing,通常會用 background.js 當中繼

    • 流程概念

      1. content_script:偵測到 projectKey 後,透過 chrome.runtime.sendMessage 丟出去。
      2. background.js:接收並暫存 projectKey。因為可能會有多個 tab 開啟 GitLab MR 頁面,所以把 tabId 一起傳給 background 做對應。
      3. popup 頁面(Vue 頁面):打開 popup 時,向 background 要資料。
    • content_script.js

    const projectKey = getProjectKey();
    // 傳給 background
    chrome.runtime.sendMessage({
    action: "setProjectKey",
    projectKey,
    });

    
    - background.js
    ```javascript
    // 用物件存放每個 tab 的 projectKey
    const projectKeysByTab = {};
    
    // 收到 content_script 的訊息
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
       if (message.action === "setProjectKey" && sender.tab) {
           projectKeysByTab[sender.tab.id] = message.projectKey;
       }
    
       if (message.action === "getProjectKey" && message.tabId) {
           sendResponse({ projectKey: projectKeysByTab[message.tabId] });
       }
    });
    
    • (popup 頁面接收示範)
    chrome.runtime.sendMessage(
     { action: "getProjectKey", tabId: activeTab.id },
     (response) => {
       if (response && response.projectKey) {
         console.log("拿到 projectKey:", response.projectKey);
         projectKey.value = response.projectKey;
       } else {
         console.log("這個 tab 還沒 projectKey");
       }
     }
    );
    
  5. 專注來調整 popup 頁面吧!

    1. 畫面加上專案名稱,優化使用者確認目前範本在哪個專案
    <div class="font-bold">{{ projectKey }} 範本文字</div>
    
    <script setup>
    const projectKey = ref('default')
    </script>
    
    1. 改寫 fetch,只專注取得所有範本 && 建立一個變數存下取得的數值
      const templates = ref({})
      
      async function fetchData() {
        let { reviewTemplates } = await chrome.storage.sync.get(['reviewTemplates']);
        templates.value = reviewTemplates || {};
      }
      
    2. 改寫 onMounted,讓它可以依據 tab 取得對應的專案名稱
      onMounted(async () => {
        await fetchData()
      
        // 先取得當前作用中的 tab
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
          const activeTab = tabs[0];
      
          chrome.runtime.sendMessage(
            { action: "getProjectKey", tabId: activeTab.id },
            (response) => {
              if (response && response.projectKey) {
                console.log("拿到 projectKey:", response.projectKey);
                projectKey.value = response.projectKey;
              } else {
                console.log("這個 tab 還沒 projectKey");
              }
            }
          );
        });
      })
      
    3. 監聽 projectKey,如果有變動就要重新取得對應的範本
      watch(projectKey, () => {
        const reviewTemplate =
          templates.value?.[projectKey.value] ??
          templates.value?.default ??
          '';
      
        templateContent.value = reviewTemplate
        initialTemplateContent.value = reviewTemplate
      }, { immediate: true })
      
    4. 改寫一下 saveTemplate,主要就是把原本儲存 string 改為 object
      async function saveTemplate() {
        const template = templateContent.value.trimEnd()
        if (template.length > LIMIT_TEXT_LENGTH) {
          error.value = '文字太長,請縮短一點~'
          return
        };
        const unknownKey = extractPlaceholders(template).filter(k => !availableKey.includes(k));
      
        if (unknownKey.length) {
          error.value = `發現未知變數:${unknownKey.join(', ')}`
          return
        };
        const { reviewTemplates = {} } = await chrome.storage.sync.get(['reviewTemplates']);
        await chrome.storage.sync.set({
          reviewTemplates: { ...reviewTemplates, [projectKey.value]: template }
        });
        templateContent.value = template
        initialTemplateContent.value = template
        error.value = ''
      }
      
    5. 調整一下 applyTemplate,讓它即使在抓不到 template 的情況下不會執行錯誤
      function applyTemplate(template = '', data) {...}
      

實測

https://ithelp.ithome.com.tw/upload/images/20250928/20153928B7DvcrNBOA.pnghttps://ithelp.ithome.com.tw/upload/images/20250928/20153928RqNewKpvke.png

參考文件


上一篇
Day 17:規劃 ─ 支援多專案 Merge Request 的差異格式
下一篇
Day 19:介紹 Message Passing(content ↔ background ↔ popup 溝通)
系列文
從零開始改善工作之 Chrome Extension: MR 通知文字小工具22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言