NILAクローム拡張モジュール


このガイドでは、Blinknotes、ニュース記事、研究論文、ブログ記事、または長いコメントから何かを要約するNLPを使用して軽量アプリを作成するつもりです.クロム拡張の定義の側面を議論し、エッジケースへの解決策を実装し、クロム開発者によって課せられたコンベンションに従う.

マニフェスト


マニフェストは、アプリケーションのプロパティ、パーミッション、およびスクリプトを定義します.
マニフェスト.JSON
{
  "name": "Blinknotes",
  "version": "0.0.0.1",
  "description": "A lightweight tool to summarize news articles, research papers, blog posts, or long comments.",
  "permissions": ["storage", "contextMenus", "*://*/*"],
  "background": {
    "scripts": ["scripts/background.js"],
    "persistent": false
  },
  "browser_action": {
  "default_popup": "popup.html",
  "default_icon": {
    "16": "images/brain16.png",
    "32": "images/brain32.png",
    "48": "images/brain48.png",
    "128": "images/brain128.png"
    }
  },
  "icons": {
    "16": "images/brain16.png",
    "32": "images/brain32.png",
    "48": "images/brain48.png",
    "128": "images/brain128.png"
  },
  "manifest_version": 2
}

アイコン


ペイント.NETは基本的な画像編集に最適です、それはPhotoshopの機能のほとんどを持っていますが、学びやすく、無料です.私はそれを使用してバナーと16 x 16、32 x 32、48 x 48、128 x 128ピクセルファイルを作成します.
イメージ/旗.PNG

イメージ/BRAIN 16.PNG

イメージ/ブレイン32 .PNG

イメージズ.PNG

イメージ/BRAIN 128.PNG

オプションメニュー



比較的簡単に、マニフェスト内のオプションHTMLファイルを定義し、対応するJS/CSSを追加します.
ポップアップ.HTML
<!DOCTYPE html>
<html >   
  <head style="background-color:white;">
  <link rel="stylesheet" href="/css/style.css">     
  </head>
  <body >
    <img src="images/banner.png" alt="" width="170px" height="60px" style = "background-color:white; padding: 0px 0px 0px 0px;margin-left:10px;">
      <div  style="border-bottom:none; padding:0px 0px 0px 0px;">    
        <div style = "width: 200px; height:40px;">
        <label for="myRange" class="short-text ">Relative Length - 2</label>
        <input  style = "width: 150px; " type="range" min="1" max="10" value="2" class="slider glow-on-hover" id="myRange"> 
        </div>
      </div>
  </div>
  <script src="/scripts/popup.js"></script>    
  </body>
</html>
ポップアップ.js
let slider = document.getElementById("myRange");
chrome.storage.local.get('length', function(data) { 
    if (data.length === undefined) {
        chrome.storage.local.set({
            length: 2
        }, function() {});
        return;
    }
    let length_value = data.length;
    let text = document.querySelector(".short-text");
    text.innerHTML = "Relative Length - " + length_value;
    slider.setAttribute('value', length_value);
});
slider.oninput = function() {
    let text = document.querySelector(".short-text");
    text.innerHTML = "Relative Length - " + this.value;
    chrome.storage.local.set({
        length: this.value
    }, function() {});
}
スタイル.CSS
body {
  width:200px;
  height:140px;
  background-color:#fff;
  border:none;
  margin:0px;
  font-family: Calibri;
}
div {
  text-align:left;
  background-color:#f4f4f4;
  padding-left:10px;
  padding-top: 5px;
  padding-bottom:5px;
}
p {
  font-size:14px;
}
label {
  font-size:14px;
  line-height:15px;
}
input {
  line-height:15px;
}
.short-text {
  line-height:20px;
}
.slider {
  -webkit-appearance: none;
  width: 100%;
  height: 3px;
  border-radius: 5px;  
  background: #000000;
  outline: none;
  opacity: 0.7;
  -webkit-transition: .2s;
  transition: opacity .2s;
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 15px;
  border-radius: 50%; 
  background: #FFFFFF;
  cursor: pointer;
  border:2px solid black;
}
.slider::-moz-range-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #FFFFFF;
  border:2px solid black;
  cursor: pointer;
}
.glow-on-hover {
  border: none;
  outline: none;
  color: #111;    
  cursor: pointer;
  position: relative;
  z-index: 0;
  border-radius: 10px;
}
.glow-on-hover:before {
  content: '';
  background: linear-gradient(45deg, #E8EBF2, #E4DCEF, #f4f4f4,   #E3E8F2, #D3E9ED, #B0CBE8, #E4E1F2, #C7D1EA,  #f4f4f4);
  position: absolute;
  top: -2px;
  left:-2px;
  background-size: 400%;
  z-index: -1;
  filter: blur(5px);
  width: calc(100% + 4px);
  height: calc(100% + 4px);
  animation: glowing 20s linear infinite;
  opacity: 0;
  transition: opacity .3s ease-in-out;
  border-radius: 10px;
  color:#111;    
}
.glow-on-hover:active {
  color:#fff;
}
.glow-on-hover:active:after {
  background: transparent;
}
.glow-on-hover:hover:before {
  opacity: 1;
  color:#111;
}
.glow-on-hover:after {
  z-index: -1;
  content: '';
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  border-radius: 10px;
  color:#111;
}
@keyframes glowing {
  0% { background-position: 0 0; }
  50% { background-position: 400% 0; }
  100% { background-position: 0 0; }
}
.glow-on-hover-check {    
  border: none;
  outline: none;    
  position: relative;
  z-index: 0;
  border-radius: 10px;
}
.glow-on-hover-check:before {
  content: '';
  background: linear-gradient(45deg, #E8EBF2, #E4DCEF, #f4f4f4,   #E3E8F2, #D3E9ED, #B0CBE8, #E4E1F2, #C7D1EA,  #f4f4f4);
  position: absolute;
  top: -2px;
  left:-2px;
  background-size: 400%;
  z-index: -1;
  filter: blur(5px);
  width: calc(100% + 4px);
  height: calc(100% + 4px);
  animation: glowing 20s linear infinite;
  opacity: 0;
  transition: opacity .3s ease-in-out;
  border-radius: 10px;
}
.glow-on-hover-check:active:after {
  background: transparent;
}
.glow-on-hover-check:hover:before {
  opacity: 1;
}
.glow-on-hover-check:after {
  z-index: -1;
  content: '';
  position: absolute;
  width: 100%;
  height: 100%;   
  left: 0;
  top: 0;
  border-radius: 10px;
}

背景スクリプト



一般的なパターンは、ブラウザインスタンスごとに1つの背景スクリプトです.バックグラウンドスクリプトは、アプリケーションをインストールし、新しいタブを開き、タブを閉じ、URLを更新し、コンテキストメニューを右クリックしますが、現在のタブのDOMにアクセスできません.
強調表示されたテキストまたは全体の記事に応じて、この背景スクリプトは2つのエンドポイントでテキスト要約APIにつながります.これは、長さパラメータを決定するためにクロムストレージを使用し、URLまたは生のテキストを送信します.発火し、APIから応答を受信すると、背景スクリプトは、コンテンツスクリプトのリスナーにメッセージを送信することによってDOMを更新します.
背景.js
let failure_message = "Unable to summarize text.";
let failure_message_blank = "To generate a summary, increase the summary length through the extension toolbar settings.";
let url = "";
let request;
let injected_tabs = []
chrome.runtime.onInstalled.addListener(function() {
    let contextMenuItem = {
        id: "summarize",
        title: "Blinknotes",
        contexts: ["page", "selection"]
    };
    chrome.contextMenus.create(contextMenuItem);
});
chrome.contextMenus.onClicked.addListener(function(info, tab) {
    let length = "0.1";
    chrome.storage.local.get('length', function(data) {
        length = (data.length / 20).toString();
    });
    chrome.tabs.query({
        active: true,
        lastFocusedWindow: true
    }, tabs => {
        url = tabs[0].url;
    });
    if (info.menuItemId == "summarize") {
        chrome.storage.local.get('in_progress', function(data) {
            if (data.in_progress == undefined || data.in_progress == false) {
                chrome.storage.local.set({
                    in_progress: true
                }, function() {
                    chrome.tabs.sendMessage(tab.id, {
                        name: "create_window",
                        content: {}
                    }, {}, function(res) {});
                    chrome.tabs.executeScript({
                        code: "window.getSelection().toString();"
                    }, function(selection) {
                        if (isNaN(length)) length = "0.1";
                        if (selection == "") {
                            request = new XMLHttpRequest();
                            request.open("POST", "https://text-summarize-api.herokuapp.com/url/", true);
                            request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                            let params = "url=" + url + "&length=" + length;
                            request.send(params);
                            request.onreadystatechange = function() {
                                if (request.readyState == 4 && request.status == 200) {
                                    var response = request.responseText;
                                    let a = response.split("\n");
                                    let first_paragraph = "";
                                    if (a[1] == "") {
                                        first_paragraph = a[0];
                                        response = "";
                                        for (let i = 1; i < a.length; i++) response += a[i];
                                    }
                                    if (first_paragraph != "")
                                        first_paragraph = first_paragraph.replace(/(\r\n|\n|\r)/gm, "");
                                    response = response.replace(/(\r\n|\n|\r)/gm, "");
                                    if (response == "" && first_paragraph == "")
                                        chrome.tabs.sendMessage(tab.id, {
                                            name: "request_succeed",
                                            first_paragraph: "",
                                            content: failure_message_blank
                                        }, {}, function(res) {
                                            chrome.storage.local.set({
                                                in_progress: false
                                            }, function() {});
                                        });
                                    else
                                        chrome.tabs.sendMessage(tab.id, {
                                            name: "request_succeed",
                                            first_paragraph: first_paragraph,
                                            content: response
                                        }, {}, function(res) {
                                            chrome.storage.local.set({
                                                in_progress: false
                                            }, function() {});
                                        });
                                } else if (request.readyState == 4) {
                                    chrome.tabs.sendMessage(tab.id, {
                                        name: "request_failed",
                                        first_paragraph: "",
                                        content: failure_message
                                    }, {}, function(res) {
                                        chrome.storage.local.set({
                                            in_progress: false
                                        }, function() {});
                                    });
                                }
                            }
                        } else {
                            if (isNaN(length)) length = "0.1"
                            request = new XMLHttpRequest();
                            request.open("POST", "https://text-summarize-api.herokuapp.com/text/", true);
                            request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                            let params = "text=" + encodeURIComponent(selection) + "&length=" + length;
                            request.send(params);
                            request.onreadystatechange = function() {
                                if (request.readyState == 4 && request.status == 200) {
                                    var response = request.responseText;
                                    response = response.replace(/(\r\n|\n|\r)/gm, "");
                                    if (response == "")
                                        chrome.tabs.sendMessage(tab.id, {
                                            name: "request_succeed",
                                            first_paragraph: "",
                                            content: failure_message_blank
                                        }, {}, function(res) {
                                            chrome.storage.local.set({
                                                in_progress: false
                                            }, function() {});
                                        });
                                    else
                                        chrome.tabs.sendMessage(tab.id, {
                                            name: "request_succeed",
                                            first_paragraph: "",
                                            content: response
                                        }, {}, function(res) {
                                            chrome.storage.local.set({
                                                in_progress: false
                                            }, function() {});
                                        });
                                } else if (request.readyState == 4) {
                                    chrome.tabs.sendMessage(tab.id, {
                                        name: "request_failed",
                                        first_paragraph: "",
                                        content: failure_message
                                    }, {}, function(res) {
                                        chrome.storage.local.set({
                                            in_progress: false
                                        }, function() {});
                                    });
                                }
                            }
                        }
                    });
                });
            }
        });
    }
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    if (injected_tabs.includes(tabId)) return;
    injected_tabs.push(tabId)
    chrome.tabs.executeScript({
        file: 'scripts/content.js'
    }, _ => {
        chrome.runtime.lastError;
        injected_tabs = injected_tabs.filter(item => item !== tabId)
    })
});
chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
    injected_tabs = injected_tabs.filter(item => item !== tabId)
});

コンテンツスクリプト



コンテンツスクリプトは、任意の1つのタブで実行することができますし、そのページのDOMへのアクセスがあります.コンテンツスクリプトは、バックグラウンドスクリプトを介してタブに注入されるか、マニフェストで定義され、特定のURLパターンで自動的に実行されます.このアプリのために我々は、ページ上の一時的なモーダル要素を作成し、更新するコンテンツスクリプトを使用して手動注射を行います.
このコンテンツスクリプトは、ページ上でドラッグ可能なモーダルを処理するリスナーを作成します.リスナーに送信されたメッセージの種類、CreateCountウィンドウ、RequestCount成功またはRequestCountによって、リスナーは既定のローディング状態でモーダルを作成します.
コンテンツ.js
if (typeof modal_x === 'undefined') {
    let modal_x = "";
    let modal_y = "";
    let styleString = `.chrome-extension-modal-content{background-color:#fefefe;margin:auto;position:absolute;z-index:999998;padding:5px;border:1px solid #888;width:40%;justify-content:center;align-items:center;overflow:auto;max-height:500px}.chrome-extension-modal-content p{padding:30px;font-size:15px;font-family:Calibri,Candara,Segoe,Segoe UI,Optima,Arial,sans-serif}.chrome-extension-modal-loading{display:flex;justify-content:center;align-items:center}.chrome-extension-modal-loading .dot{position:relative;width:.5em;height:.5em;margin:.3em;border-radius:50%;padding:0}.chrome-extension-modal-loading .dot::before{position:absolute;content:"";width:100%;height:100%;background:inherit;border-radius:inherit;animation:wave 2s ease-out infinite}.chrome-extension-modal-loading .dot:nth-child(1){background:#7ef9ff}.chrome-extension-modal-loading .dot:nth-child(1)::before{animation-delay:.2s}.chrome-extension-modal-loading .dot:nth-child(2){background:#89cff0}.chrome-extension-modal-loading .dot:nth-child(2)::before{animation-delay:.4s}.chrome-extension-modal-loading .dot:nth-child(3){background:#4682b4}.chrome-extension-modal-loading .dot:nth-child(3)::before{animation-delay:.6s}.chrome-extension-modal-loading .dot:nth-child(4){background:#0f52ba}.chrome-extension-modal-loading .dot:nth-child(4)::before{animation-delay:.8s}.chrome-extension-modal-loading .dot:nth-child(5){background:navy}.chrome-extension-modal-loading .dot:nth-child(5)::before{animation-delay:1s}@keyframes wave{50%,75%{transform:scale(2.5)}100%,80%{opacity:0}}.chrome-extension-close{color:#aaa;background-color:#fff;float:right;font-size:28px;font-weight:700;padding:10px}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer}`;
    let modal_inner_html_string = `<button class="chrome-extension-close">&times;</button> <br> <br> <br> <br><div class="chrome-extension-modal-loading"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div></div> <br> <br> <br> <br>`;
    let modal_html_string = `<div class="chrome-extension-modal-content" >` + modal_inner_html_string +` </div>`;
    const dragElement = function(elmnt) {
        var pos1 = 0,
            pos2 = 0,
            pos3 = 0,
            pos4 = 0;
        elmnt.onmousedown = dragMouseDown;
        elmnt.style.left = modal_x + "px";
        elmnt.style.top = modal_y + "px";

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
            elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }
    const fadeOutLoader = function(callback) {
        var fadeTarget = document.getElementsByClassName("chrome-extension-modal-loading")[0];
        if (fadeTarget === undefined) return;
        if (!fadeTarget.style.opacity) {
            fadeTarget.style.opacity = 1;
        } else
            fadeTarget.style.opacity = 1;
        var fadeEffect = setInterval(function() {
            if (fadeTarget.style.opacity > 0) {
                fadeTarget.style.opacity -= 0.1;
            } else {
                clearInterval(fadeEffect);
                fadeTarget.remove();
                callback();
            }
        }, 100);
    }
    const createElementFromHTML = function(htmlString) {
        var div = document.createElement('div');
        div.innerHTML = htmlString.trim();
        return div.firstChild;
    }
    const addStyle = function(styleString) {
        const style = document.createElement('style');
        style.textContent = styleString;
        document.head.append(style);
    }
    var listener = function(request, options, sendResponse) {
        var display_result = function() {
            var modal_content = document.getElementsByClassName("chrome-extension-modal-content")[0];
            if (request.first_paragraph != "" && request.content != "")
                modal_content.innerHTML = "<button class='chrome-extension-close'>&times;</button>" + "<p>" + request.first_paragraph + "<br><br>" + request.content + "</p>";
            else if (request.content != "")
                modal_content.innerHTML = "<button class='chrome-extension-close'>&times;</button>" + "<p>" + request.content + "</p>";
            var span = document.getElementsByClassName("chrome-extension-close")[0];
            span.onclick = function() {
                modal_content.style.display = "none";
            };
        };
        if (request.name == "create_window") {
            addStyle(styleString);
            modal_content = document.getElementsByClassName("chrome-extension-modal-content")[0];
            if (modal_content == null) {
                let modal_element = createElementFromHTML(modal_html_string);
                document.body.append(modal_element);
            } else {
                modal_content.innerHTML = modal_inner_html_string;
            }
            var span = document.getElementsByClassName("chrome-extension-close")[0];
            span.onclick = function() {
                chrome.storage.local.set({
                    in_progress: false
                }, function() {
                });
                modal_content.style.display = "none";
            };
            var modal_content = document.getElementsByClassName("chrome-extension-modal-content")[0];
            modal_content.style.display = "block";
            dragElement(modal_content);
            sendResponse();
        } else if (request.name == "request_failed") {
            fadeOutLoader(display_result);
            sendResponse();
        } else if (request.name == "request_succeed") {
            fadeOutLoader(display_result);
            sendResponse();
        } else sendResponse();
        return true;
    }
    document.addEventListener("contextmenu", function(event) {
        modal_x = event.pageX;
        modal_y = event.pageY;
        if (!chrome.runtime.onMessage.hasListener(listener)) {
            chrome.runtime.onMessage.addListener(listener);
        }
    });
}

テスト




エッジケース


アプリケーションが予期しないエラーを生成したり、無限ループに巻き込まれる場合があります.このセクションでは、それぞれがバックグラウンドで処理される方法を簡単に説明します.JSとコンテンツ.js
つのエラーは、非同期とタイムゲートウェイのAPI呼び出しのために発生します.複数のタブから同時に保留中に複数の呼び出しを持つことができます-これは処理することが可能ですが、それはかなりの作業が必要です.この状況のために、我々は一度に最大1の呼び出しにアプリを制限します.我々は、ブールのインテンシティの進行状況を追跡するためにストレージを使用します.呼び出しが発火されるとき、それをtrueにセットしてください、全部のプロセスが完了したあと、それをfalseに戻してください.inount進行がtrueである間、APIを再呼び出しするどんな試みも無効にされます.
重複した内容スクリプトを同じページに注入するとき、別のエラーが発生します.これを解決するには、バックグラウンドスクリプトのInjectedCounterタブを追跡し、タブIDを含まない場合のみ注入します.
つの最後のエラーは、コンテンツスクリプトがアプリケーションの正常なフローを中断する例外を生成するときに発生します.すべての注入、Call =\Chrome . runtime . lasterErrorでコールバックを追加します.これは単に呼び出しスタックを通過するのではなく、このタイプのエラーを読み、無視するでしょう.
あなたは、バグ修正で創造的な取得することができます、ユニークでハッキングソリューションを使用してエッジケースを処理します.だけではなく、全体のアプリのデザインからあまりにも遠くに迷うし、徹底的にテストする前にテストしてください.

結論


Chrome Webストアのアプリケーションの広大な生態系で、Chrome拡張機能ができることに驚くほど少ない制限があります.このガイドでは、公式のドキュメントから重要なポイントを概説しようとします.あなたが公開する準備ができたら、あなたのアプリを市場にどのようにガイドラインを提供するようなアプリを使用することができます.