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实现复制到剪贴板功能,兼容所有浏览器

猜你喜欢

转载自www.cnblogs.com/yusimiao29/p/9155658.html
今日推荐