chrome-extension:Copy with URL(將復制的選中文字轉變為超鏈接文本)


1. 簡介

這是一個chrome-extension,實現了一個增強文字復制的功能,我給它起名叫做Copy with URL。

它能夠將選中的純文本變身為超鏈接文本,你可以將超鏈接文本粘貼在其他支持插入超鏈接文本的編輯器中,比如Evernote,Word或者Excel。

有了Copy with URL,從此在需要插入超鏈接文本的時候,就不再需要復制文本>粘貼文本>復制鏈接>編輯鏈接+各種切換程序窗口的繁瑣操作。

如果你有頻繁使用超鏈接文本的習慣,那么它將極大提升你的工作效率,讓你更加專注於工作本身。

項目代碼見Copy-with-URL,涉及到的技術見文末總結。

2. 功能

使用它和正常的復制粘貼文本基本沒有區別,兩張圖來展示該擴展的使用方法和效果。

 

step1:選中網頁的純文本文字(文字原本是超鏈接文本也可以)后單機鼠標右鍵,在右鍵菜單中點擊Copy with URL選項。此時,文字及其所在網頁的URL就以超鏈接形式存儲在了系統剪貼板中。

step2:這就是將剛復制的超鏈接文本粘貼在word中的效果。受實現方式的限制,粘貼的內容消除了原本的字體等格式。

3. 項目闡述(相關工作)

該擴展源碼基於chrome-extension:Copy as Markdown Quotation進行改動開發,這里首先對原作者表示感謝!

Copy as Markdown Quotation實現了將純文本轉變為Markdown格式的超鏈接文本,粘貼在Word中的效果就是一段純文本后跟了一個本質上是文本的鏈接。

本擴展將純文本和URL組合成超鏈接文本,雖然只是一個小改動,但是對於我來說,還是廢了幾番周折,所以我將開發過程和所用到的技術記錄在此。

4. 實現

4.1 總體設計

需要明確的是:超鏈接文本和文本的區別在於超鏈接文本實際上是一個HTML頁的<a>標簽。知道了這一點后,開發的目標就定位成將在HTML頁內選定或創建的 <a> 標簽復制到剪貼板。其實,我之前很長時間都沒弄明白超鏈接文本和純文本的區別,以至於不知道如何下手。如果用純文本寫一段 <a href="url">value</a> ,把它復制到任一編輯器,得到的仍然是這段文本,因為這並不是一個DOM對象(不確定這樣說夠不夠准確)。

此擴展首先獲取選中對象,之后對選中的對象進行解析,提取出文本,標簽等信息。得到信息后,再將文本和URL組合成<a>標簽,最后將標簽復制到系統剪貼板。

js實現復制到剪貼板功能,兼容所有瀏覽器介紹了用js實現復制功能的多種方案,Copy as Markdown Quotation采用的是js自帶的 document.execCommand('copy');方法,但該方法貌似只能復制純文本內容,反正我沒有改動成功。

本工程使用的是clipboard.js框架,選它是因為看中它輕量級,易操作的特性。關鍵是它最終實現了復制帶鏈接的文本。

4.2 工程目錄

該工程包含的源代碼文件較少,所有的文件都在一個文件夾下。具體的文件有:

| manifest.json

| background.js

| background-cp.js

| clipboard.min.js

| icon16.png

| icon48.png

| icon128.png

4.3 manifest.json

manifest.json對於一個chrome-extension是很重要的文件,它的存在決定了extension的形態。

{
   "background": {
      "scripts": [ "background-cp.js" ]
   },
   "description": "Copy text in web page with URL",
   "icons": {
      "128": "icon128.png",
      "16": "icon16.png",
      "48": "icon48.png"
   },
   "manifest_version": 2,
   "minimum_chrome_version": "24",
   "name": "Copy with URL",
   "permissions": [ "tabs", "storage", "contextMenus", "notifications", "file://*/","http://*/", "https://*/" ],
   "update_url": "https://clients2.google.com/service/update2/crx",
   "version": "0.4.1"
}

在上面的manifest.json中,值得一提的是"permissions"中的contextMenus為該擴展獲取了操作瀏覽器右鍵菜單的權限。"background"屬性內的background-cp.js指示了該擴展在后台運行的js代碼,"background"還可以指定擴展運行在后台的html頁面。沒有指定html的話,瀏覽器會為extension創建一個空白的html頁。

4.4 backgroud-cp.js

該擴展的所有行為都定義在了background-cp.js內,代碼有點長,但只有幾個函數而已。

document.write("<script type='text/javascript' src='clipboard.min.js'></script>");  

var get_selection = function() {

    var selection = document.getSelection();
    var text = selection.toString();
    var node = selection.getRangeAt(0).startContainer;
    var uri = node.baseURI || document.documentURI;
    var parent = node.parentElement;
    var whiteSpace = (parent && window.getComputedStyle(parent)['white-space']);
    var index;
    var ext;
    var is_code = function(elem) {
        if (!elem) return false;

        // Is the element monospace?
        if (window.getComputedStyle(elem)['white-space'].toLowerCase() == 'pre') {
            return true;
        }

        // Is the element generated by CodeMirror?
        if (elem.className.toLowerCase().split(' ').indexOf('codemirror') >= 0) {
            return true;
        }

        return false;
    }
    var pre = is_code(parent);
    console.log(pre)

    var get_frag = function(parent) {
        var frag, sibling, nephew;

        if (!parent) {
            return null;
        }

        frag = parent.id || parent.name;

        if (frag) {
            return frag;
        }

        sibling = parent.previousSibling;

        while(sibling) {
            frag = sibling.id || sibling.name;

            if (frag) {
                return frag;
            }

            nephew = sibling.children && sibling.children[0];
            frag = nephew && (nephew.id || nephew.name);

            if (frag) {
                return frag;
            }

            sibling = sibling.previousSibling;
        }
    }
    var fileName;
    var orig_frag;
    var frag;

    // Remove the fragment from the url and find the better one only if the
    // original one was not semantically significant.
    index = uri.lastIndexOf('#');
    console.log(index)
    orig_frag = index >= 0 ? uri.substring(index + 1) : null;
    if (!orig_frag || orig_frag.indexOf('/') < 0) { // Assume the fragment is siginificant if it contains '/'.
        uri = index >= 0 ? uri.substring(0, index) : uri;
        while(!frag && parent) {
            frag = get_frag(parent);
            parent = parent.parentElement;
        }
    }

    // Get extension from the url
    index = uri.lastIndexOf('/');
    fileName = index >= 0 ? uri.substring(index + 1) : '';
    index = fileName.lastIndexOf('.');
    ext = index >= 0 ? fileName.substring(index + 1) : '';

    if (frag) {
        uri += '#' + frag;
    }

    return {
        text: text,
        uri: uri,
        pre: pre,
        ext: ext
    };
}

var ltrim_lines = function(str) {
    return str.replace(/^(\s*\n)*/, '');
}

var rtrim = function(str) {
    return str.replace(/[\s\n]*$/, '');
}


var copy_as_markdown_quot = function (args) {
    chrome.tabs.executeScript( {
          code: "(" + get_selection + ")();" 
    }, function(selections) {
        var text = rtrim(ltrim_lines(selections[0].text));
        var uri = selections[0].uri;
        var pre = selections[0].pre;
        var ext = selections[0].ext;
        if (text) {
            lines = text.split('\n');
            result = '';
            if (pre) result += '> ```' + ext + '\n';
            for (var i = 0; i < lines.length; i++) {
                result +=  lines[i] + '\n';
            }
            if (pre) result += '> ```\n'
            copyTextToClipboard(result,uri);
        }
    });
};

function copyTextToClipboard(text,uri) {
    var copyFrom,agent,body;

    copyFrom = document.createElement("a");
    copyFrom.setAttribute("id","target");
    copyFrom.setAttribute("href",uri);
    copyFrom.innerHTML = text;
    agent = document.createElement("button");
    body = document.getElementsByTagName('body')[0];
    body.appendChild(copyFrom);
    body.appendChild(agent);
  // 采用Clipboard.js方案
  // trouble:沒有可以傳入的HTML元素,但我們可以動態創建一個DOM對象作為代理
    var clipboard = new ClipboardJS(agent, {
        target: function() {
            return document.querySelector('#target');
        }
    });

    clipboard.on('success', function(e) {
        console.log(e);
    });

    clipboard.on('error', function(e) {
        console.log(e);
    });
    agent.click();
    // copyFrom.focus();
    // copyFrom.select();  // 問題所在 無法對copyFrom對象使用select()方法
    // document.execCommand('copy'); // copy動態創建的<a>失敗
    body.removeChild(copyFrom);
    body.removeChild(agent);
    

}

chrome.contextMenus.create({
    title: "Copy with URL",
    contexts: ['selection'],
    onclick: copy_as_markdown_quot
});

 關於background-cp.js的幾點說明:

1) 代碼開頭的 document.write() 方法包含了clipboard框架,這樣就可以在background-cp.js內調用clipboard.js的方法。

2)  chrome.contextMenus.create({...}); 在瀏覽器的右鍵菜單中創建子選項,點擊選項觸發 copy_as_markdown_quot() 函數。

3)  chrome.tabs.executeScript({code:"..."},function(){...}) 可以在當前頁面執行一段嵌入式的js代碼,需要事先在"permissions"內聲明"tabs"權限。本例中,要在tab內執行的就是 get_selection() 函數,該方法解析出鼠標選中區域 document.getSelection(); 的文本及URL等信息,向回調函數傳入一個selections參數。

4) 回調函數對selection對象進行格式化處理,隨后調用 copyTextToClipboard() 函數,將格式化后的文本復制到剪貼板。

5) 對 copyTextToClipboard() 中復制方法的改動是本擴展的核心工作。該方法首先創建一個 <a> 標簽,該標簽實際上創建在隱藏的background.html頁中。然后實例化ClipboardJS對象復制 <a> 元素。

6) clipboard.js/demo/中給出了幾個應用clipboard.js的例子,本擴展所用的方法基於function-target.html進行改動。

clipboard需要傳入一個DOM selectorHTML element或者list of HTML elements來初始化。在function-target例中,點擊實例化ClipboardJS的button可以復制目標元素(帶格式),這也為實現本擴展目標提供了可能。

7) 問題在於無法在tab頁或者background.html頁找到一個合適的元素來初始化ClipboardJS對象,而且我也不想笨拙地通過點擊除右鍵菜單選項的方式來實現復制。

解決這個問題的方法是在background頁動態創建一個隱藏的button作為代理,如 agent = document.createElement("button"); 所示。除此之外, body.appendChild(agent); 將button嵌入到body當中。最后對該button調用一次 click()方法,復制生效,大功告成!

5. future

1) 計划加入清除文本格式的子菜單選項

2) 突破某些高冷網頁的復制限制 例如:百度文庫,知乎

3) 添加Copy as Markdown quotation子選項 參照 穿越.crx

6. 技術總結

 本擴展實現了一個簡單的文本復制功能,應用到了這么幾項技術:

1) chrome-extension創建右鍵菜單選項

2) chrome-extension在tab頁執行一段js代碼

3) 動態創建HTML元素,設置元素屬性

4) 獲取並解析鼠標選中區域

5) Clipboard.js

6) 在一個JS文件內引入另一個JS文件內的方法

資料

【1】clipboard.js 介紹

【2】js文件中引用其他js文件

【3】Copy as Markdown Quotation Chrome插件

【4】js實現復制到剪貼板功能,兼容所有瀏覽器

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM