How to replace email addresses with mailto links i

2019-01-15 04:37发布

Imagine this: you come across a webpage that says "Just send a message to user@example.com" but to actually send the email, you need to highlight the address and then cut and paste it into the recipient field of a new compose window of your email client of choice.

Obviously life would be easier if it were simply a mailto: link, so you could click on it and have a new message created automatically. How do I build an extension that turns email addresses into clickable mailto: links?

I was originally going to ask if there was an extension to enable similar functionality for unlinked Twitter @username mentions but I thought this email address problem would be a simpler situation.

1条回答
仙女界的扛把子
2楼-- · 2019-01-15 05:24

Use innerHTML to replace the email - No, this breaks page's, especially because event listeners are removed and attributes are also replaced.

Recursively loop through all nodes:

  • Loop in the reverse order, to prevent conflicts when you modify the DOM.
  • For each item, check it's nodeType value.
    • If .nodeType === 1 (element), call the function again (recursion).
    • If .nodeType === 3 (text node):
      1. Use a regular expression and the exec method to find one email address. Use the result's .index property to know the starting position of the email address, and result[0].length to know the length of the address.
      2. Use the node's splitText method cut the text node in three parts.
      3. Create an <a> element.
      4. Append the email's text node (the second text node from the previous) to this anchor. It's automatically removed from the document.
      5. Insert this link before the third node.

Demo

Not a chrome extension, but it shows how a chrome extension would behave: http://jsfiddle.net/ckw89/

Chrome extension

(the regexp is based on MongoEngine's EmailField pattern):

script.js

 // Initiate recursion
 wrapLink(document.body);

 function wrapLink(elem) { // elem must be an element node
     var nodes = elem.childNodes
       , i = nodes.length
       , regexp = /([-!\x23$%&'*+\/=?^_`{}|~0-9A-Z]+(\.[-!\x23$%&'*+\/=?^_`{}|~0-9A-Z]+)*|^"([\x01-\x08\x0b\x0c\x0e-\x1f!\x23-\\[\\]-\x7f]|\\[\x01-011\x0b\x0c\x0e-\x7f])*")@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}/i
       , node, emailNode, a, result;
     while (node = nodes[--i]) {
         if (node.nodeType === 1) {
             // Skip anchor tags, nested anchors make no sense
             if (node.nodeName.toUpperCase() !== 'A')
                wrapLink(node);
         } else if (node.nodeType === 3) {
             // 1: Please note that the regexp has NO global flag,
             //    and that `node.textContent` shrinks when an address is found
             while (result = regexp.exec(node.textContent)) {
                 // 2: Contact <SPLIT> me@example.com for details
                 node = node.splitText(result.index);

                 // 2: Contact <SPLIT>me@example.com<SPLIT> for details
                 node = node.splitText(result[0].length);

                 // me@example.com
                 emailNode = node.previousSibling

                 // 3. Create link
                 a = document.createElement('a');
                 a.href = 'mailto:' + result[0];

                 // 4: Append emailNode
                 a.appendChild(emailNode);

                 // 5: Insert before
                 elem.insertBefore(a, node);
             }
         }
     }
 }

This script will work immediately when used as a Content script, because its only interaction with the page is the DOM. For completeness, here's the contents of the manifest.json file:

{
    "name": "Turns email addresses in `mailto:`s",
    "version": "1",
    "version_version": 2,
    "content_scripts": [{
        "matches": ["*://*/*"],
        "js": ["script.js"]
    }]
}

Performance notice

The current script replaces all nodes in the live document. Consider moving the root node (eg <body>) to a document fragment before manipulating.

查看更多
登录 后发表回答