Advertisement
Guest User

Focus On The User.org "don't be evil" bookmarklet

a guest
Jan 24th, 2012
182
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**************
  2.                    The bookmarklet
  3.                                       ***************/
  4. javascript: (function () {
  5.     if (window.__dbe) {
  6.         return;
  7.     }
  8.     window.__dbe = true;
  9.     if (document.body.childNodes.length === 0) {
  10.         alert("Due to a bug in Google Chrome (issue #93199), this tool will not work " + "for searches conducted via Chrome's address bar. To fix the issue, do " + "any of the following:\n\n" + "* Search from www.google.com instead of from the address bar.\n\n" + "* Go to File -> New Tab, right click on the \"Google Search\" Chrome " + "app and click \"Remove from Chrome\". This will fix searches from the " + "address bar.\n\n" + "* Use another browser, such as Firefox.");
  11.     }
  12.     var anchor = document.getElementsByTagName('script')[0];
  13.     var script = document.createElement('script');
  14.     script.src = '//focusontheuser.org/dontbeevil/script.js?' + (new Date().getTime());
  15.     anchor.parentNode.insertBefore(script, anchor);
  16. })();
  17.  
  18.  
  19. /**************
  20.                    The referenced JavaScript file
  21.                                                      ***************/
  22. (function () {
  23.  
  24.     if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
  25.         alert("Unfortunately, this tool does not currently work in Internet Explorer. " + "Please try another browser such as Firefox, Chrome, or Safari.");
  26.         return;
  27.     }
  28.  
  29.     var anchor = document.getElementsByTagName('script')[0];
  30.     if (!anchor) {
  31.         return;
  32.     }
  33.  
  34.     var link = document.createElement('link');
  35.     link.rel = 'stylesheet';
  36.     link.type = 'text/css';
  37.     link.href = '//focusontheuser.org/dontbeevil/style.css?3';
  38.     anchor.parentNode.insertBefore(link, anchor);
  39.  
  40.     var script = document.createElement('script');
  41.     script.src = '//ajax.googleapis.com/ajax/libs/mootools/1.4.1/mootools-yui-compressed.js';
  42.     script.onload = main;
  43.     anchor.parentNode.insertBefore(script, anchor);
  44.  
  45.     // Main Application
  46.     function main() {
  47.         // A custom pseudo selector for finding highlighted typeahead entries.
  48.         Slick.definePseudo('highlighted', function () {
  49.             return this.getStyle('background-color') != 'transparent';
  50.         });
  51.  
  52.         // Debounce to prevent updatePage from being called too often.
  53.         var debouncedUpdatePage = debounce(updatePage);
  54.  
  55.         // The main results div will serve as our flag. Store a reference to this
  56.         // node, and get it by id in a loop. Update the page whenever it changes.
  57.         var getFlag = function () {
  58.                 return $('rso');
  59.             };
  60.         var oldFlag = getFlag();
  61.         var compare = function () {
  62.                 var newFlag = getFlag();
  63.                 if (newFlag != oldFlag) {
  64.                     oldFlag = newFlag;
  65.                     debouncedUpdatePage();
  66.                 }
  67.             };
  68.  
  69.         setInterval(compare, 100);
  70.         setupTypeahead();
  71.         updatePage();
  72.     }
  73.  
  74.     function updatePage() {
  75.         updateTypeahead();
  76.         updateRelatedBox();
  77.         updateResultStats();
  78.         updateContentLinks();
  79.         updateRelatedLinks();
  80.     }
  81.  
  82.     // When the user searchs for a Twitter username including the leading @,
  83.     // redirect them directly to Twitter if their search shows up on the page.
  84.     function redirectIfTwitterUsername() {
  85.         var input = $$('input[name=q]')[0];
  86.         if (input && (/^@[A-Za-z0-9_]+$/).test(input.value)) {
  87.             var username = input.get('value').substr(1).toLowerCase();
  88.             $$('li.g div.s div.f cite').some(function (item) {
  89.                 var path = item.get('text').toLowerCase();
  90.                 if (path == ("twitter.com/" + username) || path == ("twitter.com/#!/" + username) || path == ("twitter.com/#!/@" + username)) {
  91.                     document.location = "https://twitter.com/" + username;
  92.                 }
  93.             });
  94.         }
  95.     }
  96.  
  97.     // Attach some event listeners to the main text input, so we can navigate when
  98.     // the user uses enter to select a result from the typeahead.
  99.     function setupTypeahead() {
  100.         var href;
  101.         var input = $('gbqfq') || $$('input[name=q]')[0];
  102.         if (input) {
  103.             input.addEvents({
  104.                 keyup: function () {
  105.                     var span = $$('body > table tr:highlighted span:contains(".com")')[0];
  106.                     href = span && span.retrieve('href') || '';
  107.                 },
  108.                 keydown: function (event) {
  109.                     if (event.key != 'enter') {
  110.                         return;
  111.                     }
  112.                     if (href) {
  113.                         window.open(href);
  114.                         event.stop();
  115.                     } else {
  116.                         redirectIfTwitterUsername();
  117.                     }
  118.                 }
  119.             });
  120.         }
  121.     }
  122.  
  123.     // Look for any typeahead entries that contain '.com' in a span, since this is
  124.     // one differentiating feature of the Google+ entries. For each result, perform
  125.     // a full search for the text of that entry, and 'replace' the Google+ results
  126.     // with the highest ranked result as returned by the full search.
  127.     function updateTypeahead() {
  128.         var spans = $$('body > table tr span:contains(".com")');
  129.         spans.each(function (span) {
  130.             var row = span.getParent('tr');
  131.             var cell = row.getLast('td');
  132.             var span = cell.getLast('span');
  133.             // Some entries might contain .com, but not be the double height G+ entries.
  134.             if (!span) {
  135.                 return;
  136.             }
  137.             var name = cell.getFirst('span').get('text');
  138.             getSearchResult(name, function (result) {
  139.                 if (!result) {
  140.                     return;
  141.                 }
  142.                 var img = row.getElement('img');
  143.                 var item = row.getParent('tr');
  144.                 // Attach a click listener, which will be used to navigate on select, to
  145.                 // each entry in the typeahead. Results are reused, so only attach once.
  146.                 if (item && !item.retrieve('attached')) {
  147.                     item.store('attached', true);
  148.                     item.addEvent('click', function (event) {
  149.                         window.open(span.retrieve('href'));
  150.                     });
  151.                 }
  152.                 // If we have a photo for this item, update the typeahead entry, otherwise
  153.                 // memoize the existing photo in our result object for later use.
  154.                 result.photo ? (img.src = result.photo) : (result.photo = img.src);
  155.                 // Update the site that's displayed.
  156.                 span.set('text', ' \xb7 ' + result.site);
  157.                 // Store a reference to the href needed for navigation (used with 'enter').
  158.                 span.store('href', result.href);
  159.             });
  160.         });
  161.     }
  162.  
  163.     // This tweaks the text in the right hand column to say "People and Pages from
  164.     // the Social Web", rather than "People and Pages on Google+". The column gets
  165.     // completely replaced on every search, so we need to run this every time.
  166.     function updateRelatedBox() {
  167.         var fieldset = $('aerhs');
  168.         if (fieldset) {
  169.             var legend = fieldset.getFirst('legend');
  170.             legend && legend.set('text', 'People and Pages from the Social Web');
  171.         }
  172.     }
  173.  
  174.     // Show a little blurb with a link in the result stats area. This also needs to
  175.     // be updated along with every query.
  176.     function updateResultStats() {
  177.         var stats = $$('#appbar :contains("seconds")').pop();
  178.         if (stats) {
  179.             // Probably only need to do this once, but it doesn't hurt.
  180.             stats.setStyle('overflow', 'visible');
  181.             stats.getParent('div').setStyle('overflow', 'visible');
  182.             stats.adopt([
  183.             new Element('span').adopt([
  184.             document.createTextNode('\xb7 '), new Element('span', {
  185.                 'class': 'dbe_stats'
  186.             }).adopt([
  187.             document.createTextNode('results improved by '), new Element('a', {
  188.                 href: 'http://www.focusontheuser.org',
  189.                 text: 'FocusOnTheUser.org'
  190.             })])])]);
  191.         }
  192.     }
  193.  
  194.     // Find the special Google+ links attached directly to search results in the
  195.     // middle column, and replace them with the most relevant social result.
  196.     function updateContentLinks() {
  197.         var link = $$('#rso a[href*="plus.google.com"].l')[0];
  198.         if (!link) {
  199.             return;
  200.         }
  201.         var text = link.get('text').replace(' - Google+', '');
  202.         if (!text) {
  203.             return;
  204.         }
  205.         var item = link.getParent('li.g');
  206.         if (!item) {
  207.             return;
  208.         }
  209.         getSearchResult(text, function (result) {
  210.             if (!result) {
  211.                 return;
  212.             }
  213.             // Memoize the node, and apply the style from the item we're replacing.
  214.             if (!result.plusNode) {
  215.                 result.plusNode = result.node.set('style', item.get('style')).adopt(result.links);
  216.             }
  217.             // Plus links are indented.. this seems to be a pretty simple way to make
  218.             // sure we only replace the correct links.
  219.             if (parseInt(item.getStyle('margin-left'), 10) > 0) {
  220.                 // Oh, why not use a little superfluous animation.
  221.                 item.get('morph').start({
  222.                     opacity: [1, 0]
  223.                 }).chain(function () {
  224.                     var morph = result.plusNode.get('morph').set({
  225.                         opacity: 0
  226.                     });
  227.                     result.plusNode.replaces(item).highlight('#fea');
  228.                     morph.start({
  229.                         opacity: [0, 1]
  230.                     });
  231.                 });
  232.             }
  233.         });
  234.     }
  235.  
  236.     // Find the Google+ links in the right hand column and replace them with the
  237.     // most relevant social results, including all links to social sites that show
  238.     // up in the top 100 results, ranked by Google.
  239.     function updateRelatedLinks() {
  240.         var links = $$('#rhs_block a[href*="profiles.google.com"]');
  241.         links.each(function (link) {
  242.             var text = (link.get('text') || '').replace(/\.com$/, '');
  243.             if (!text) {
  244.                 return;
  245.             }
  246.             var item = link.getParent('table');
  247.             if (!item) {
  248.                 return;
  249.             }
  250.             getSearchResult(text, function (result) {
  251.                 if (!result) {
  252.                     return;
  253.                 }
  254.                 if (!result.profileBlock) {
  255.                     result.profileBlock = (result.site != 'plus.google.com') ? buildProfileBlockMarkup(result, item.getElement('img').get('src')) : item.clone();
  256.                 }
  257.                 item.get('morph').start({
  258.                     opacity: [1, 0]
  259.                 }).chain(function () {
  260.                     var div = new Element('div', {
  261.                         'class': 'dbe_results'
  262.                     });
  263.                     var morph = div.get('morph').set({
  264.                         opacity: 0
  265.                     });
  266.                     div.adopt([result.profileBlock, result.links]).replaces(item).highlight('#fea');
  267.                     morph.start({
  268.                         opacity: [0, 1]
  269.                     });
  270.                 });
  271.             })
  272.         });
  273.     }
  274.  
  275.     // For the most part, we're just going to use the markup as returned to us by
  276.     // Google's servers, and inject it into the various places on the page. We do
  277.     // want to modify it a little bit, adding photos, like / tweet buttons, etc,
  278.     // and then we'll memoize the node for later use.
  279.     function buildProfileBlockMarkup(result, defaultPhoto) {
  280.         var node = result.node;
  281.         var site = result.site;
  282.         var text = node.getElement('h3');
  283.         var link = text.getElement('a');
  284.         var href = link.get('href');
  285.         link.set('title', link.get('text')).set('text', result.text);
  286.  
  287.         // Remove the 'Person Shared This' box.
  288.         var div = text.getNext().getNext();
  289.         div && div.dispose();
  290.  
  291.         var src = '',
  292.             html = '',
  293.             span;
  294.         if (site == 'facebook.com') {
  295.             // The Facebook like button is dumb, and even shows up for profiles,
  296.             // where subscribe should be the default action - override for zuck.
  297.             var buttonType = href.indexOf('/zuck') > 0 ? 'subscribe' : 'like';
  298.             src = '//connect.facebook.net/en_US/all.js#xfbml=1';
  299.             html = '<div href="' + href + '" class="fb-' + buttonType + '" data-show-faces="false" data-layout="button_count" data-width="120" data-send="false"></div>';
  300.         } else if (site == 'twitter.com') {
  301.             src = '//platform.twitter.com/widgets.js';
  302.             html = '<a href="' + href + '" class="twitter-follow-button" data-show-screen-name="false"></a>';
  303.             // Remove the repetitive twitter default sign up text.
  304.             span = node.getElement('span:contains("Sign up for Twitter")');
  305.             span && span.set('text', span.get('text').replace(/(\s+|^)Sign up for Twitter[\s\w\-]+\(@\w+\)\.\s+/, ''));
  306.         } else if (site == 'linkedin.com') {
  307.             span = node.getElement('span:contains("professional profile on LinkedIn")');
  308.             if (span) {
  309.                 var blurb = span.getPrevious();
  310.                 span.set('text', blurb.get('text'));
  311.                 blurb.dispose();
  312.             }
  313.         }
  314.  
  315.         if (src) {
  316.             var script = new Element('script', {
  317.                 src: src,
  318.                 onload: function () {
  319.                     script.dispose();
  320.                 }
  321.             }).inject(anchor, 'top');
  322.             new Element('div', {
  323.                 'class': 'dbe_button',
  324.                 html: html
  325.             }).inject(text, 'bottom');
  326.         }
  327.  
  328.         new Element('a', {
  329.             href: href
  330.         }).adopt(new Element('img', {
  331.             src: result.photo || defaultPhoto,
  332.             'class': 'dbe_img'
  333.         })).inject(text.getNext(), 'top');
  334.  
  335.         return new Element('ul', {
  336.             'class': 'dbe_first'
  337.         }).adopt(node);
  338.     }
  339.  
  340.     // This function builds the little div of &middot separated links that shows
  341.     // up below the most relevant search result in the middle and right columns.
  342.     function buildLinksDiv(results) {
  343.         var nodes = [];
  344.         results.each(function (result, index) {
  345.             nodes.push(new Element('a', {
  346.                 href: result.href,
  347.                 text: siteMap[result.site].name,
  348.                 title: link.get('text')
  349.             }));
  350.             if (index < results.length - 1) {
  351.                 nodes.push(document.createTextNode(' \xb7 '));
  352.             }
  353.         });
  354.  
  355.         return new Element('div', {
  356.             'class': 'dbe_extra'
  357.         }).adopt(nodes);
  358.     }
  359.  
  360.     // Functions for fetching data
  361.     // Given a query string and a callback, this function will perform an
  362.     // asynchronous Google search for the top 100 results, parse the response into
  363.     // HTMLLIElement DOM nodes, filter that set down to only meaningful socially
  364.     // relevent results, fetch photos for the linked profiles of those results,
  365.     // and invoke the callback provided with a sorted list of result objects.
  366.     var getSearchResult = (function () {
  367.  
  368.         // Figure out where the search result passed in points to, by looking at the
  369.         // first anchor element inside the node.
  370.         var getHrefFromNode = function (node) {
  371.                 var link = $(node).getElement('a');
  372.                 return link ? link.get('href') : '';
  373.             }
  374.  
  375.             // Retrieve a photo for the provided result object, based either on a
  376.             // specially crafted Google image query (for Twitter), or on a query to
  377.             // Google's own richsnippets web tool.
  378.         var getPhoto = function (result, callback) {
  379.                 if (!siteMap[result.site].photo) {
  380.                     return callback();
  381.                 }
  382.                 var href = result.href;
  383.                 var url = '//www.google.com/';
  384.                 if (result.site == 'twitter.com') {
  385.                     // Get Twitter profile pic by google image searching for the twitter
  386.                     // profile's url, e.g. "site:twitter.com/britneyspears", and then
  387.                     // extracting the first result. This seems to usually get the right thing,
  388.                     // though often the wrong size.
  389.                     url += 'search?gbv=2&tbm=isch&fp=1&biw=1&bih=1&tch=1&q=' + encodeURIComponent('site:' + href);
  390.                     xhr(url, function (text) {
  391.                         var matches = unescape(text).match(/imgurl\\=(https?:\/\/[^.]+\.twimg\.com\/profile_images\/\d+\/[^\\&]+)/);
  392.                         var match = matches ? matches[1].replace(/_(?:bigger|mini|normal|reasonably_small)/, '').replace(/\.jpg$/, '_normal.jpg') : '';
  393.                         callback(match);
  394.                     });
  395.                 } else {
  396.                     url += 'webmasters/tools/richsnippets?view=cse&url=' + encodeURIComponent(href);
  397.                     xhr(url, function (text) {
  398.                         var matches = text.match(/(?:image|photo)\s*=\s*(.+?)\s*<br\s*\/>/m);
  399.                         var match = matches ? matches[1] : '';
  400.                         callback(match);
  401.                     });
  402.                 }
  403.             };
  404.  
  405.         // Out of all the results in the result set, find the socially meaningful
  406.         // ones, discarding all the rest. This function is used to return a sorted
  407.         // list of results for each query, and should aggressively filter out
  408.         // unrelated results.
  409.         var findMeaningfulResults = function (text, results) {
  410.                 var hrefs = [];
  411.                 var sources = [];
  412.                 var matched = {};
  413.                 return results.filter(function (result) {
  414.                     // Filter out any results without an href.
  415.                     var href = getHrefFromNode(result);
  416.                     if (!href) {
  417.                         return false;
  418.                     }
  419.                     // Filter down to only the sites we care about.
  420.                     var matches = href.match(siteMatcher);
  421.                     if (!matches) {
  422.                         return false;
  423.                     }
  424.                     // Make sure we haven't already found a higher ranked site match.
  425.                     var site = matches[1];
  426.                     var alreadyMatched = matched[site];
  427.                     if (alreadyMatched) {
  428.                         return false;
  429.                     }
  430.                     // If we have a whitelist for this site, make sure it matches.
  431.                     var whitelist = siteMap[site].whitelist;
  432.                     if (whitelist && !whitelist.test(href)) {
  433.                         return false;
  434.                     }
  435.                     // If we have a blacklist for this site, make sure it doesn't match.
  436.                     var blacklist = siteMap[site].blacklist;
  437.                     if (blacklist && blacklist.test(href)) {
  438.                         return false;
  439.                     }
  440.                     // Store at most one match per site, including site name and href.
  441.                     hrefs.push(href);
  442.                     sources.push(site);
  443.                     return (matched[site] = true);
  444.                 }).map(function (node, index) {
  445.                     return {
  446.                         node: node,
  447.                         text: text,
  448.                         href: hrefs[index],
  449.                         site: sources[index]
  450.                     }
  451.                 });
  452.             };
  453.  
  454.         // This crazy function will parse a Google XHR response, pulling it apart
  455.         // into chunks, and turning each of those individual chunks into a DOM node
  456.         // that can then be analyzed / inserted into the DOM.
  457.         var parseResponseText = function (text) {
  458.                 var offset = 0;
  459.                 var openTag = "\\x3c!--m--\\x3e";
  460.                 var closeTag = "\\x3c!--n--\\x3e";
  461.                 var tagLength = 14;
  462.                 var getNextResult = function () {
  463.                         var result = null;
  464.                         var open = text.indexOf(openTag, offset);
  465.                         var close = text.indexOf(closeTag, offset);
  466.                         var nextOpen = text.indexOf(openTag, open + tagLength);
  467.                         var nextClose = text.indexOf(closeTag, close + tagLength);
  468.                         if (nextOpen < close) {
  469.                             close = nextOpen;
  470.                             offset = nextClose + tagLength;
  471.                         } else {
  472.                             offset = close + tagLength;
  473.                         }
  474.                         if (open >= 0 && close >= 0) {
  475.                             var html = unescape(text.substring(open + tagLength, close));
  476.                             result = new Element('ul', {
  477.                                 html: html
  478.                             }).getFirst();
  479.                         }
  480.                         return result;
  481.                     };
  482.                 var results = [];
  483.                 var nextResult = null;
  484.                 while (nextResult = getNextResult()) {
  485.                     results.push(nextResult);
  486.                 }
  487.                 return results;
  488.             };
  489.  
  490.         var cache = {};
  491.         return function (query, callback) {
  492.             if (query in cache) {
  493.                 return callback(cache[query]);
  494.             }
  495.             var url = '//www.google.com/search?q=' + encodeURIComponent(query) + '&xhr=t&fp=1&psi=1&num=100&complete=0&pws=0';
  496.             xhr(url, function (text) {
  497.                 var results = findMeaningfulResults(query, parseResponseText(text));
  498.                 var result = results[0];
  499.                 if (result) {
  500.                     result.links = buildLinksDiv(results.slice(1));
  501.                     getPhoto(result, function (photo) {
  502.                         result.photo = photo || '';
  503.                         cache[query] = result;
  504.                         callback(result);
  505.                     });
  506.                 }
  507.             });
  508.         };
  509.  
  510.     })();
  511.  
  512.     // Generic Utilities
  513.     function debounce(func) {
  514.         var timer = null;
  515.         return function () {
  516.             clearTimeout(timer);
  517.             timer = setTimeout(func, 500);
  518.         }
  519.     }
  520.  
  521.     var unescape = (function () {
  522.         var regex = /\\x22|\\x26|\\x27|\\x3c|\\x3d|\\x3e/g;
  523.         var callback = function (match) {
  524.                 switch (match) {
  525.                 case "\\x22":
  526.                     return '"';
  527.                 case "\\x26":
  528.                     return "&";
  529.                 case "\\x27":
  530.                     return "'";
  531.                 case "\\x3c":
  532.                     return "<";
  533.                 case "\\x3d":
  534.                     return "=";
  535.                 case "\\x3e":
  536.                     return ">";
  537.                 }
  538.             };
  539.         return function (str) {
  540.             return str.replace(regex, callback);
  541.         };
  542.     })();
  543.  
  544.     function xhr(url, callback) {
  545.         var req = google.xhr();
  546.         req.open("GET", url, true);
  547.         req.onreadystatechange = function (evt) {
  548.             if (req.readyState === 4) {
  549.                 callback(req.responseText);
  550.             }
  551.         };
  552.         req.send(null);
  553.     }
  554.  
  555.     // Sites
  556.     var siteMap = {
  557.         'crunchbase.com': {
  558.             name: 'CrunchBase',
  559.             whitelist: /(?:^|[\/.])crunchbase\.com\/(person|company)\/(\w+)\/?$/i
  560.         },
  561.         'facebook.com': {
  562.             name: 'Facebook',
  563.             photo: true,
  564.             whitelist: /(?:^|[\/.])facebook\.com\/([a-zA-Z0-9.]+)\/?$/i
  565.         },
  566.         'flickr.com': {
  567.             name: 'Flickr',
  568.             whitelist: /(?:^|[\/.])flickr\.com\/people\/(\w+)\/?$/i
  569.         },
  570.         'foursquare.com': {
  571.             name: 'Foursquare',
  572.             whitelist: /(?:^|[\/.])foursquare\.com\/(\w+)\/?$/i
  573.         },
  574.         'friendfeed.com': {
  575.             name: 'FriendFeed',
  576.             whitelist: /(?:^|[\/.])friendfeed\.com\/(\w+)\/?$/i
  577.         },
  578.         'github.com': {
  579.             name: 'GitHub',
  580.             whitelist: /(?:^|[\/.])github\.com\/(\w+)\/?$/i
  581.         },
  582.         'linkedin.com': {
  583.             name: 'LinkedIn',
  584.             photo: true,
  585.             whitelist: /(?:^|[\/.])linkedin\.com\/in\/(\w+)\/?$/i
  586.         },
  587.         'myspace.com': {
  588.             name: 'MySpace',
  589.             photo: true,
  590.             whitelist: /(?:^|[\/.])myspace\.com\/(\w+)\/?$/i
  591.         },
  592.         'plus.google.com': {
  593.             name: 'Google+',
  594.             whitelist: /(?:^|[\/.])plus\.google\.com\/(\w+)\/?$/i
  595.         },
  596.         'quora.com': {
  597.             name: 'Quora',
  598.             photo: true,
  599.             whitelist: /(?:^|[\/.])quora\.com\/([\w\-]+)\/?$/i,
  600.             blacklist: /\/(who|what|when|where|why|how)\-/i
  601.         },
  602.         'stackoverflow.com': {
  603.             name: 'Stack Overflow',
  604.             whitelist: /(?:^|[\/.])stackoverflow\.com\/users\/(\w+)\/?/i
  605.         },
  606.         'tumblr.com': {
  607.             name: 'Tumblr',
  608.             whitelist: /(?:^|[\/.])(\w+)\.tumblr\.com\/?$/,
  609.             blacklist: /\/tagged\//
  610.         },
  611.         'twitter.com': {
  612.             photo: true,
  613.             name: 'Twitter',
  614.             whitelist: /(?:^|[\/.])twitter\.com\/(\w+)\/?$/i,
  615.             blacklist: /\/statuses\//
  616.         },
  617.         'youtube.com': {
  618.             name: 'YouTube',
  619.             whitelist: /(?:^|[\/.])youtube\.com\/user\/(\w+)(?:\/featured)?\/?$/
  620.         }
  621.     };
  622.  
  623.     var siteMatcher = (function () {
  624.         var sites = [];
  625.         for (var site in siteMap) {
  626.             sites.push(site);
  627.         }
  628.         return new RegExp('[\/.](' + sites.join('|').replace('.', '\\.') + ')');
  629.     })();
  630.  
  631. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement