1/* 2 * share-selection: Medium like popover menu to share on Twitter or by email any text selected on the page 3 * 4 * -- Requires jQuery -- 5 * -- AMD compatible -- 6 * 7 * Author: Xavier Damman (@xdamman) 8 * GIT: https://github.com/xdamman/share-selection 9 * MIT License 10 */ 11 12(function($) { 13 14 var SelectionSharer = function(options) { 15 16 var self = this; 17 18 options = options || {}; 19 if(typeof options == 'string') 20 options = { elements: options }; 21 22 this.sel = null; 23 this.textSelection=''; 24 this.htmlSelection=''; 25 26 this.appId = $('meta[property="fb:app_id"]').attr("content") || $('meta[property="fb:app_id"]').attr("value"); 27 this.url2share = $('meta[property="og:url"]').attr("content") || $('meta[property="og:url"]').attr("value") || window.location.href; 28 29 this.getSelectionText = function(sel) { 30 var html = "", text = ""; 31 var sel = sel || window.getSelection(); 32 if (sel.rangeCount) { 33 var container = document.createElement("div"); 34 for (var i = 0, len = sel.rangeCount; i < len; ++i) { 35 container.appendChild(sel.getRangeAt(i).cloneContents()); 36 } 37 text = container.textContent; 38 html = container.innerHTML 39 } 40 self.textSelection = text; 41 self.htmlSelection = html || text; 42 return text; 43 }; 44 45 this.selectionDirection = function(selection) { 46 var sel = selection || window.getSelection(); 47 var range = document.createRange(); 48 if(!sel.anchorNode) return 0; 49 range.setStart(sel.anchorNode, sel.anchorOffset); 50 range.setEnd(sel.focusNode, sel.focusOffset); 51 var direction = (range.collapsed) ? "backward" : "forward"; 52 range.detach(); 53 return direction; 54 }; 55 56 this.showPopunder = function() { 57 self.popunder = self.popunder || document.getElementById('selectionSharerPopunder'); 58 59 var sel = window.getSelection(); 60 var selection = self.getSelectionText(sel); 61 62 if(sel.isCollapsed || selection.length < 10 || !selection.match(/ /)) 63 return self.hidePopunder(); 64 65 if(self.popunder.classList.contains("fixed")) 66 return self.popunder.style.bottom = 0; 67 68 var range = sel.getRangeAt(0); 69 var node = range.endContainer.parentNode; // The <p> where the selection ends 70 71 // If the popunder is currently displayed 72 if(self.popunder.classList.contains('show')) { 73 // If the popunder is already at the right place, we do nothing 74 if(Math.ceil(self.popunder.getBoundingClientRect().top) == Math.ceil(node.getBoundingClientRect().bottom)) 75 return; 76 77 // Otherwise, we first hide it and the we try again 78 return self.hidePopunder(self.showPopunder); 79 } 80 81 if(node.nextElementSibling) { 82 // We need to push down all the following siblings 83 self.pushSiblings(node); 84 } 85 else { 86 // We need to append a new element to push all the content below 87 if(!self.placeholder) { 88 self.placeholder = document.createElement('div'); 89 self.placeholder.className = 'selectionSharerPlaceholder'; 90 } 91 92 // If we add a div between two <p> that have a 1em margin, the space between them 93 // will become 2x 1em. So we give the placeholder a negative margin to avoid that 94 var margin = window.getComputedStyle(node).marginBottom; 95 self.placeholder.style.height = margin; 96 self.placeholder.style.marginBottom = (-2 * parseInt(margin,10))+'px'; 97 node.parentNode.insertBefore(self.placeholder); 98 } 99 100 // scroll offset 101 var offsetTop = window.pageYOffset + node.getBoundingClientRect().bottom; 102 self.popunder.style.top = Math.ceil(offsetTop)+'px'; 103 104 setTimeout(function() { 105 if(self.placeholder) self.placeholder.classList.add('show'); 106 self.popunder.classList.add('show'); 107 },0); 108 109 }; 110 111 this.pushSiblings = function(el) { 112 while(el=el.nextElementSibling) { el.classList.add('selectionSharer'); el.classList.add('moveDown'); } 113 }; 114 115 this.hidePopunder = function(cb) { 116 cb = cb || function() {}; 117 118 if(self.popunder == "fixed") { 119 self.popunder.style.bottom = '-50px'; 120 return cb(); 121 } 122 123 self.popunder.classList.remove('show'); 124 if(self.placeholder) self.placeholder.classList.remove('show'); 125 // We need to push back up all the siblings 126 var els = document.getElementsByClassName('moveDown'); 127 while(el=els[0]) { 128 el.classList.remove('moveDown'); 129 } 130 131 // CSS3 transition takes 0.6s 132 setTimeout(function() { 133 if(self.placeholder) document.body.insertBefore(self.placeholder); 134 cb(); 135 }, 600); 136 137 }; 138 139 this.show = function(e) { 140 setTimeout(function() { 141 var sel = window.getSelection(); 142 var selection = self.getSelectionText(sel); 143 if(!sel.isCollapsed && selection && selection.length>10 && selection.match(/ /)) { 144 var range = sel.getRangeAt(0);
145 var topOffset = range.getBoundingClientRect().top - 5; 146 var top = topOffset + window.scrollY - self.$popover.height(); 147 var left = 0; 148 if(e) { 149 left = e.pageX; 150 } 151 else { 152 var obj = sel.anchorNode.parentNode; 153 left += obj.offsetWidth / 2; 154 do { 155 left += obj.offsetLeft; 156 } 157 while(obj = obj.offsetParent); 158 } 159 switch(self.selectionDirection(sel)) { 160 case 'forward': 161 left -= self.$popover.width(); 162 break; 163 case 'backward': 164 left += self.$popover.width(); 165 break; 166 default: 167 return; 168 } 169 self.$popover.removeClass("anim").css("top", top+10).css("left", left).show(); 170 setTimeout(function() { 171 self.$popover.addClass("anim").css("top", top); 172 }, 0); 173 } 174 }, 10); 175 }; 176 177 this.hide = function(e) { 178 self.$popover.hide(); 179 }; 180 181 this.smart_truncate = function(str, n){ 182 if (!str || !str.length) return str; 183 var toLong = str.length>n, 184 s_ = toLong ? str.substr(0,n-1) : str; 185 s_ = toLong ? s_.substr(0,s_.lastIndexOf(' ')) : s_; 186 return toLong ? s_ +'...' : s_; 187 }; 188 189 this.getRelatedTwitterAccounts = function() { 190 var usernames = []; 191 192 var creator = $('meta[name="twitter:creator"]').attr("content") || $('meta[name="twitter:creator"]').attr("value"); 193 if(creator) usernames.push(creator); 194 195 196 // We scrape the page to find a link to http(s)://twitter.com/username 197 var anchors = document.getElementsByTagName('a'); 198 for(var i=0, len=anchors.length;i<len;i++) { 199 if(anchors[i].attributes.href && typeof anchors[i].attributes.href.value == 'string') { 200 var matches = anchors[i].attributes.href.value.match(/^https?:\/\/twitter\.com\/([a-z0-9_]{1,20})/i) 201 if(matches && matches.length > 1 && ['widgets','intent'].indexOf(matches[1])==-1) 202 usernames.push(matches[1]); 203 } 204 } 205 206 if(usernames.length > 0) 207 return usernames.join(','); 208 else 209 return ''; 210 }; 211 212 this.shareTwitter = function(e) { 213 e.preventDefault(); 214 215 var text = "â"+self.smart_truncate(self.textSelection.trim(), 114)+"â"; 216 var url = 'http://twitter.com/intent/tweet?text='+encodeURIComponent(text)+'&related='+self.relatedTwitterAccounts+'&url='+encodeURIComponent(window.location.href); 217 218 // We only show the via @twitter:site if we have enough room 219 if(self.viaTwitterAccount && text.length < (120-6-self.viaTwitterAccount.length)) 220 url += '&via='+self.viaTwitterAccount; 221 222 var w = 640, h=440; 223 var left = (screen.width/2)-(w/2); 224 var top = (screen.height/2)-(h/2)-100; 225 window.open(url, "share_twitter", 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left); 226 self.hide(); 227 return false; 228 }; 229 230 this.shareFacebook = function(e) { 231 e.preventDefault(); 232 var text = self.htmlSelection.replace(/<p[^>]*>/ig,'\n').replace(/<\/p>| /ig,'').trim(); 233 234 var url = 'https://www.facebook.com/dialog/feed?' + 235 'app_id='+self.appId + 236 '&display=popup'+ 237 '&caption='+encodeURIComponent(text)+ 238 '&link='+encodeURIComponent(self.url2share)+ 239 '&href='+encodeURIComponent(self.url2share)+ 240 '&redirect_uri='+encodeURIComponent(self.url2share); 241 var w = 640, h=440; 242 var left = (screen.width/2)-(w/2); 243 var top = (screen.height/2)-(h/2)-100; 244 245 window.open(url, "share_facebook", 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left); 246 }; 247 248 this.shareEmail = function(e) { 249 var text = self.textSelection.replace(/<p[^>]*>/ig,'\n').replace(/<\/p>| /ig,'').trim(); 250 var email = {}; 251 email.subject = encodeURIComponent("Quote from "+document.title); 252 email.body = encodeURIComponent("â"+text+"â")+"%0D%0A%0D%0AFrom: "+document.title+"%0D%0A"+window.location.href; 253 $(this).attr("href","mailto:?subject="+email.subject+"&body="+email.body); 254 self.hide(); 255 return true; 256 }; 257 258 this.render = function() { 259 var popoverHTML = '<div class="selectionSharer" id="selectionSharerPopover" style="position:absolute;">' 260 + ' <div id="selectionSharerPopover-inner">' 261 + ' <ul>' 262 + ' <li><a class="action tweet" href="" title="Share this selection on Twitter" target="_blank">Tweet</a></li>' 263 + ' <li><a class="action facebook" href="" title="Share this selection on Facebook" target="_blank">Facebook</a></li>' 264 + ' <li><a class="action email" href="" title="Share this selection by email" target="_blank"><svg width="20" height="20"><path stroke="#FFF" stroke-width="6" d="m16,25h82v60H16zl37,37q4,3 8,0l37-37M16,85l30-30m22,0 30,30"/></svg></a></li>' 265 + ' </ul>' 266 + ' </div>' 267 + ' <div class="selectionSharerPopover-clip"><span class="selectionSharerPopover-arrow"></span></div>' 268 + '</div>'; 269 270 var popunderHTML = '<div id="selectionSharerPopunder" class="selectionSharer">' 271 + ' <div id="selectionSharerPopunder-inner">' 272 + ' <label>Share this selection</label>' 273 + ' <ul>' 274 + ' <li><a class="action tweet" href="" title="Share this selection on Twitter" target="_blank">Tweet</a></li>' 275 + ' <li><a class="action facebook" href="" title="Share this selection on Facebook" target="_blank">Facebook</a></li>' 276 + ' <li><a class="action email" href="" title="Share this selection by email" target="_blank"><svg width="20" height="20"><path stroke="#FFF" stroke-width="6" d="m16,25h82v60H16zl37,37q4,3 8,0l37-37M16,85l30-30m22,0 30,30"/></svg></a></li>' 277 + ' </ul>' 278 + ' </div>' 279 + '</div>'; 280 self.$popover = $(popoverHTML); 281 self.$popover.find('a.tweet').click(self.shareTwitter); 282 self.$popover.find('a.facebook').click(self.shareFacebook); 283 self.$popover.find('a.email').click(self.shareEmail); 284 285 $('body').append(self.$popover); 286 287 self.$popunder = $(popunderHTML); 288 self.$popunder.find('a.tweet').click(self.shareTwitter); 289 self.$popunder.find('a.facebook').click(self.shareFacebook); 290 self.$popunder.find('a.email').click(self.shareEmail); 291 $('body').append(self.$popunder); 292 293 if (self.appId && self.url2share){ 294 $(".selectionSharer a.facebook").css('display','inline-block'); 295 } 296 }; 297 298 this.setElements = function(elements) { 299 if(typeof elements == 'string') elements = $(elements); 300 self.$elements = elements instanceof $ ? elements : $(elements); 301 self.$elements.mouseup(self.show).mousedown(self.hide).addClass("selectionShareable"); 302 303 self.$elements.bind('touchstart', function(e) { 304 self.isMobile = true; 305 }); 306 307 document.onselectionchange = self.selectionChanged; 308 }; 309 310 this.selectionChanged = function(e) { 311 if(!self.isMobile) return; 312 313 if(self.lastSelectionChanged) { 314 clearTimeout(self.lastSelectionChanged); 315 } 316 self.lastSelectionChanged = setTimeout(function() { 317 self.showPopunder(e); 318 }, 300); 319 }; 320 321 this.render(); 322 323 if(options.elements) { 324 this.setElements(options.elements); 325 } 326 327 }; 328 329 // jQuery plugin 330 // Usage: $( "p" ).selectionSharer(); 331 $.fn.selectionSharer = function() { 332 var sharer = new SelectionSharer(); 333 sharer.setElements(this); 334 return this; 335 }; 336 337 // For AMD / requirejs 338 // Usage: require(["selection-sharer!"]); 339 // or require(["selection-sharer"], function(selectionSharer) { var sharer = new SelectionSharer('p'); }); 340 if(typeof define == 'function') { 341 define(function() { 342 SelectionSharer.load = function (name, req, onLoad, config) { 343 var sharer = new SelectionSharer(); 344 sharer.setElements('p'); 345 onLoad(); 346 }; 347 return SelectionSharer; 348 });
349 350 } 351 else { 352 // Registering SelectionSharer as a global 353 // Usage: var sharer = new SelectionSharer('p'); 354 window.SelectionSharer = SelectionSharer; 355 } 356 357})(jQuery); 358 359
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.