1/** 2 * Social Likes 3 * http://sapegin.github.com/social-likes 4 * 5 * Sharing buttons for Russian and worldwide social networks. 6 * 7 * @requires jQuery 8 * @author Artem Sapegin 9 * @copyright 2014 Artem Sapegin (sapegin.me) 10 * @license MIT 11 */ 12 13/* global define:false, socialLikesButtons:false */ 14 15;(function (factory) { 16 // Try to register as an anonymous AMD module 17 if (typeof define === 'function' && define.amd) { 18 define(['jquery'], factory) 19 } else { 20 factory(jQuery) 21 } 22})(function ($, undefined) { 23 'use strict' 24 25 var prefix = 'sf-share' 26 var classPrefix = prefix + '-' 27 var openClass = prefix + '-opened' 28 var protocol = location.protocol === 'https:' ? 'https:' : 'http:' 29 30 /** 31 * Buttons 32 */ 33 var services = { 34 telegram: { 35 counterUrl: 'https://telegram.me/share/url?url={url}&title={title}', 36 convertNumber: function (data) { 37 return data.share.share_count 38 }, 39 popupUrl: 'https://telegram.me/share/url?url={url}&title={title}', 40 popupWidth: 600, 41 popupHeight: 430, 42 }, 43 facebook: { 44 counterUrl: 'https://graph.facebook.com/?id={url}', 45 convertNumber: function (data) { 46 return data.share.share_count 47 }, 48 popupUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}', 49 popupWidth: 600, 50 popupHeight: 359, 51 }, 52 twitter: { 53 counters: false, 54 popupUrl: 'https://twitter.com/intent/tweet?url={url}&text={title}', 55 popupWidth: 600, 56 popupHeight: 250, 57 click: function () { 58 // Add colon to improve readability 59 if (!/[.?:\-ââ]\s*$/.test(this.options.title)) { 60 this.options.title += '' 61 } 62 return true 63 }, 64 }, 65 mailru: { 66 counterUrl: protocol + '//connect.mail.ru/share_count?url_list={url}&callback=1&func=?', 67 convertNumber: function (data) { 68 for (var url in data) { 69 if (data.hasOwnProperty(url)) { 70 return data[url].shares 71 } 72 } 73 }, 74 popupUrl: 'https://connect.mail.ru/share?share_url={url}&title={title}', 75 popupWidth: 492, 76 popupHeight: 500, 77 }, 78 vkontakte: { 79 counterUrl: 'https://vk.com/share.php?act=count&url={url}&index={index}', 80 counter: function (jsonUrl, deferred) { 81 var options = services.vkontakte 82 if (!options._) { 83 options._ = [] 84 if (!window.VK) { 85 window.VK = {} 86 } 87 window.VK.Share = { 88 count: function (idx, number) { 89 options._[idx].resolve(number) 90 }, 91 } 92 } 93 94 var index = options._.length 95 options._.push(deferred) 96 $.getScript(makeUrl(jsonUrl, { index: index })).fail(deferred.reject) 97 }, 98 popupUrl: 'https://vk.com/share.php?url={url}&title={title}', 99 popupWidth: 655, 100 popupHeight: 450, 101 }, 102 odnoklassniki: { 103 counterUrl: protocol + '//connect.ok.ru/dk?st.cmd=extLike&ref={url}&uid={index}', 104 counter: function (jsonUrl, deferred) { 105 /*var options = services.odnoklassniki; 106 if (!options._) { 107 options._ = []; 108 if (!window.ODKL) { 109 window.ODKL = {}; 110 } 111 window.ODKL.updateCount = function(idx, number) { 112 options._[idx].resolve(number); 113 }; 114 } 115 116 var index = options._.length; 117 options._.push(deferred); 118 $.getScript(makeUrl(jsonUrl, { index: index })).fail(deferred.reject);*/ 119 }, 120 popupUrl: 121 'https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&service=odnoklassniki&st.shareUrl={url}', 122 popupWidth: 580, 123 popupHeight: 336, 124 }, 125 plusone: { 126 counters: false, 127 popupUrl: 'https://plus.google.com/share?url={url}', 128 popupWidth: 500, 129 popupHeight: 550, 130 }, 131 pinterest: { 132 counterUrl: protocol + '//api.pinterest.com/v1/urls/count.json?url={url}&callback=?', 133 convertNumber: function (data) { 134 return data.count 135 }, 136 popupUrl: 'https://pinterest.com/pin/create/button/?url={url}&description={title}', 137 popupWidth: 740, 138 popupHeight: 550, 139 }, 140 } 141 142 /** 143 * Counters manager 144 */ 145 var counters = { 146 promises: {}, 147 fetch: function (service, url, extraOptions) { 148 if (!counters.promises[service]) { 149 counters.promises[service] = {} 150 } 151 var servicePromises = counters.promises[service] 152 153 if (!extraOptions.forceUpdate && servicePromises[url]) { 154 return servicePromises[url] 155 } 156 157 var options = $.extend({}, services[service], extraOptions) 158 var deferred = $.Deferred() 159 var jsonUrl = options.counterUrl && makeUrl(options.counterUrl, { url: url }) 160 161 if (jsonUrl && $.isFunction(options.counter)) { 162 options.counter(jsonUrl, deferred) 163 } else if (options.counterUrl) { 164 $.getJSON(jsonUrl) 165 .done(function (data) { 166 try { 167 var number = data 168 if ($.isFunction(options.convertNumber)) { 169 number = options.convertNumber(data) 170 } 171 deferred.resolve(number) 172 } catch (e) { 173 deferred.reject() 174 } 175 }) 176 .fail(deferred.reject) 177 } else { 178 deferred.reject() 179 } 180 181 servicePromises[url] = deferred.promise() 182 return servicePromises[url] 183 }, 184 } 185 186 // jQuery plugin 187 $.fn.socialLikes = function (options) { 188 return this.each(function () { 189 var elem = $(this) 190 var instance = elem.data(prefix) 191 if (instance) { 192 if ($.isPlainObject(options)) { 193 instance.update(options) 194 } 195 } else { 196 instance = new SocialLikes( 197 elem, 198 $.extend({}, $.fn.socialLikes.defaults, options, dataToOptions(elem)) 199 ) 200 elem.data(prefix, instance) 201 } 202 }) 203 } 204 205 $.fn.socialLikes.defaults = { 206 url: window.location.href.replace(window.location.hash, ''), 207 title: document.title, 208 counters: true, 209 zeroes: false, 210 wait: 500, // Show buttons only after counters are ready or after this amount of time 211 timeout: 10000, // Show counters after this amount of time even if they arenât ready 212 popupCheckInterval: 500, 213 singleTitle: 'Share', 214 } 215 216 function SocialLikes(container, options) { 217 this.container = container 218 this.options = options 219 this.init() 220 } 221 222 SocialLikes.prototype = { 223 init: function () { 224 // Add class in case of manual initialization 225 //console.log(this.container) 226 227 //this.container.addClass(prefix); 228 229 //this.single = this.container.hasClass(prefix + '-single'); 230 231 this.initUserButtons() 232 233 this.countersLeft = 0 234 this.number = 0 235 this.container.on('counter.' + prefix, $.proxy(this.updateCounter, this)) 236 237 var buttons = this.container.children() 238 //console.log(buttons) 239 240 //this.makeSingleButton(); 241 242 this.buttons = [] 243 buttons.each( 244 $.proxy(function (idx, elem) { 245 var button = new Button($(elem), this.options) 246 this.buttons.push(button) 247 if (button.options.counterUrl) { 248 this.countersLeft++ 249 } 250 }, this) 251 ) 252 253 if (this.options.counters) { 254 this.timer = setTimeout($.proxy(this.appear, this), this.options.wait) 255 this.timeout = setTimeout($.proxy(this.ready, this, true), this.options.timeout) 256 } else { 257 this.appear() 258 } 259 }, 260 initUserButtons: function () { 261 if (!this.userButtonInited && window.socialLikesButtons) { 262 $.extend(true, services, socialLikesButtons) 263 } 264 this.userButtonInited = true 265 }, 266 /*makeSingleButton: function() { 267 if (!this.single) { 268 return; 269 } 270 271 var container = this.container; 272 //container.addClass(prefix + '_vertical'); 273 //container.wrap($('<div>', { class: prefix + '_single-w' })); 274 //container.wrapInner($('<div>', { class: prefix + '__single-container' })); 275 var wrapper = container.parent(); 276 277 // Widget
278 var widget = $('<div>', { 279 class: getElementClassNames('widget', 'single'), 280 }); 281 var button = $( 282 template( 283 '<div class="{buttonCls}">' + '<span class="{iconCls}"></span>' + '{title}' + '</div>', 284 { 285 buttonCls: getElementClassNames('button', 'single'), 286 iconCls: getElementClassNames('icon', 'single'), 287 title: this.options.singleTitle, 288 } 289 ) 290 ); 291 widget.append(button); 292 wrapper.append(widget); 293 294 widget.on('click', function() { 295 var activeClass = prefix + '__widget_active'; 296 widget.toggleClass(activeClass); 297 if (widget.hasClass(activeClass)) { 298 container.css({ 299 left: -(container.width() - widget.width()) / 2, 300 top: -container.height(), 301 }); 302 showInViewport(container); 303 closeOnClick(container, function() { 304 widget.removeClass(activeClass); 305 }); 306 } else { 307 container.removeClass(openClass); 308 } 309 return false; 310 }); 311 312 this.widget = widget; 313 },*/ 314 315 /*update: function(options) { 316 if (!options.forceUpdate && options.url === this.options.url) { 317 return; 318 } 319 320 // Reset counters 321 this.number = 0; 322 this.countersLeft = this.buttons.length; 323 if (this.widget) { 324 this.widget.find('.' + prefix + '-counter').remove(); 325 } 326 327 // Update options 328 $.extend(this.options, options); 329 for (var buttonIdx = 0; buttonIdx < this.buttons.length; buttonIdx++) { 330 this.buttons[buttonIdx].update(options); 331 } 332 },*/ 333 /*updateCounter: function(e, service, number) { 334 number = number || 0; 335 336 if (number || this.options.zeroes) { 337 this.number += number; 338 if (this.single) { 339 this.getCounterElem().text(this.number); 340 } 341 } 342 343 this.countersLeft--; 344 345 if (this.countersLeft === 0) { 346 this.appear(); 347 this.ready(); 348 } 349 },*/ 350 /*appear: function() { 351 this.container.addClass(prefix + '_visible'); 352 },*/ 353 /*ready: function(silent) { 354 if (this.timeout) { 355 clearTimeout(this.timeout); 356 } 357 this.container.addClass(prefix + '_ready'); 358 if (!silent) { 359 this.container.trigger('ready.' + prefix, this.number); 360 } 361 }, 362 getCounterElem: function() { 363 console.log(this) 364 //var elemCounter = document.querySelector 365 /*var counterElem = this.widget.find('.' + classPrefix + 'counter_single'); 366 if (!counterElem.length) { 367 counterElem = $('<span>', { 368 class: getElementClassNames('counter', 'single'), 369 }); 370 this.widget.append(counterElem); 371 }*/ 372 //return counterElem; 373 //}, 374 } 375 376 function Button(widget, options) { 377 this.widget = widget 378 this.options = $.extend({}, options) 379 this.detectService() 380 if (this.service) { 381 this.init() 382 } 383 } 384 385 Button.prototype = { 386 init: function () { 387 this.detectParams() 388 this.initHtml() 389 //setTimeout($.proxy(this.initCounter, this), 0); 390 }, 391 392 update: function (options) { 393 $.extend(this.options, { forceUpdate: false }, options) 394 this.widget.find('.' + prefix + '-counter').remove() // Remove old counter 395 this.initCounter() 396 }, 397 398 detectService: function () { 399 var service = this.widget.data('service') 400 if (!service) { 401 // class="facebook" 402 var node = this.widget[0] 403 var classes = node.classList || node.className.split(' ') 404 for (var classIdx = 0; classIdx < classes.length; classIdx++) { 405 var cls = classes[classIdx] 406 if (services[cls]) { 407 service = cls 408 break 409 } 410 } 411 if (!service) { 412 return 413 } 414 } 415 this.service = service 416 $.extend(this.options, services[service]) 417 }, 418 419 detectParams: function () { 420 var data = this.widget.data() 421 422 // Custom page counter URL or number 423 if (data.counter) { 424 var number = parseInt(data.counter, 10) 425 if (isNaN(number)) { 426 this.options.counterUrl = data.counter 427 } else { 428 this.options.counterNumber = number 429 } 430 } 431 432 // Custom page title 433 if (data.title) { 434 this.options.title = data.title 435 } 436 437 // Custom page URL 438 if (data.url) { 439 this.options.url = data.url 440 } 441 }, 442 443 initHtml: function () { 444 var options = this.options 445 var widget = this.widget 446 447 // Old initialization HTML 448 var a = widget.find('a') 449 if (a.length) { 450 this.cloneDataAttrs(a, widget) 451 } 452 453 // Button 454 var button = $('<div>', {
455 //class: this.getElementClassNames('btn'), 456 html: widget.html(), 457 }) 458 459 //console.log(this.widget); 460 461 if (options.clickUrl) { 462 var url = makeUrl(options.clickUrl, { 463 url: options.url, 464 title: options.title, 465 }) 466 467 var link = $('<a>', { 468 href: url, 469 }) 470 471 this.cloneDataAttrs(widget, link) 472 widget.replaceWith(link) 473 this.widget = link 474 widget = link 475 } else { 476 widget.on('click', $.proxy(this.click, this)) 477 } 478 479 widget.removeClass(this.service) 480 //widget.addClass(this.getElementClassNames('widget')); 481 482 // Icon 483 //button.prepend($('<div>', { class: this.getElementClassNames('icon') })); 484 485 //widget.empty().append(button); 486 this.button = button 487 }, 488 489 initCounter: function () { 490 if (this.options.counters) { 491 if (this.options.counterNumber) { 492 this.updateCounter(this.options.counterNumber) 493 } else { 494 var extraOptions = { 495 counterUrl: this.options.counterUrl, 496 forceUpdate: this.options.forceUpdate, 497 } 498 counters 499 .fetch(this.service, this.options.url, extraOptions) 500 .always($.proxy(this.updateCounter, this)) 501 } 502 } 503 }, 504 505 cloneDataAttrs: function (source, destination) { 506 var data = source.data() 507 for (var key in data) { 508 if (data.hasOwnProperty(key)) { 509 destination.data(key, data[key]) 510 } 511 } 512 }, 513 514 getElementClassNames: function (elem) { 515 return getElementClassNames(elem, this.service) 516 }, 517 518 updateCounter: function (number) { 519 number = parseInt(number, 10) || 0 520 521 var params = { 522 //class: this.getElementClassNames('counter'), 523 text: number, 524 } 525 if (!number && !this.options.zeroes) { 526 //params.class += ' ' + prefix + '-counter-empty'; 527 params.text = '' 528 } 529 530 var counterElem = this.widget.find('.' + classPrefix + 'counter') // + this.service); 531 //console.log(counterElem) 532 //console.log(counterElem.length); 533 /*if (!counterElem.length) { 534 counterElem = $('<div>', params); 535 this.widget.append(counterElem); 536 }*/ 537 538 counterElem.html(number) 539 540 this.widget.trigger('counter.' + prefix, [this.service, number]) 541 }, 542 543 click: function (e) { 544 var options = this.options 545 var process = true 546 if ($.isFunction(options.click)) { 547 process = options.click.call(this, e) 548 } 549 if (process) { 550 var url = makeUrl(options.popupUrl, { 551 url: options.url, 552 title: options.title, 553 }) 554 url = this.addAdditionalParamsToUrl(url) 555 this.openPopup(url, { 556 width: options.popupWidth, 557 height: options.popupHeight, 558 }) 559 } 560 return false 561 }, 562 563 addAdditionalParamsToUrl: function (url) { 564 var params = $.param($.extend(this.widget.data(), this.options.data)) 565 if ($.isEmptyObject(params)) { 566 return url 567 } 568 var glue = url.indexOf('?') === -1 ? '?' : '&' 569 return url + glue + params 570 }, 571 572 openPopup: function (url, params) { 573 var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 574 var dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 575 var width = window.innerWidth 576 ? window.innerWidth 577 : document.documentElement.clientWidth 578 ? document.documentElement.clientWidth 579 : screen.width 580 var height = window.innerHeight 581 ? window.innerHeight 582 : document.documentElement.clientHeight 583 ? document.documentElement.clientHeight 584 : screen.height 585 586 var left = Math.round(width / 2 - params.width / 2) + dualScreenLeft 587 var top = 0 588 if (height > params.height) { 589 top = Math.round(height / 3 - params.height / 2) + dualScreenTop 590 } 591 592 var win = window.open( 593 url, 594 'sl_' + this.service, 595 'left=' + 596 left + 597 ',top=' + 598 top + 599 ',' + 600 'width=' + 601 params.width + 602 ',height=' + 603 params.height + 604 ',personalbar=0,toolbar=0,scrollbars=1,resizable=1' 605 ) 606 if (win) { 607 win.focus() 608 this.widget.trigger('popup_opened.' + prefix, [this.service, win]) 609 var timer = setInterval( 610 $.proxy(function () { 611 if (!win.closed) { 612 return 613 } 614 clearInterval(timer) 615 this.widget.trigger('popup_closed.' + prefix, this.service) 616 }, this), 617 this.options.popupCheckInterval 618 ) 619 } else { 620 location.href = url 621 } 622 }, 623 } 624 625 /** 626 * Helpers 627 */ 628 629 // Camelize data-attributes 630 function dataToOptions(elem) { 631 function upper(m, l) { 632 return l.toUpper() 633 } 634 var options = {} 635 var data = elem.data() 636 for (var key in data) { 637 var value = data[key] 638 if (value === 'yes') { 639 value = true 640 } else if (value === 'no') { 641 value = false 642 } 643 options[key.replace(/-(\w)/g, upper)] = value 644 } 645 return options 646 } 647 648 function makeUrl(url, context) { 649 return template(url, context, encodeURIComponent) 650 } 651 652 function template(tmpl, context, filter) { 653 return tmpl.replace(/\{([^}]+)\}/g, function (m, key) { 654 // If key doesn't exists in the context we should keep template tag as is 655 return key in context ? (filter ? filter(context[key]) : context[key]) : m 656 }) 657 } 658 659 function getElementClassNames(elem, mod) { 660 var cls = classPrefix + elem 661 return cls + ' ' + cls + '-' + mod 662 } 663 664 function closeOnClick(elem, callback) { 665 function handler(e) { 666 if ((e.type === 'keydown' && e.which !== 27) || $(e.target).closest(elem).length) { 667 return 668 } 669 elem.removeClass(openClass) 670 doc.off(events, handler) 671 if ($.isFunction(callback)) { 672 callback() 673 } 674 } 675 var doc = $(document) 676 var events = 'click touchstart keydown' 677 doc.on(events, handler) 678 } 679 680 function showInViewport(elem) { 681 var offset = 10 682 if (document.documentElement.getBoundingClientRect) { 683 var left = parseInt(elem.css('left'), 10) 684 var top = parseInt(elem.css('top'), 10) 685 686 var rect = elem[0].getBoundingClientRect() 687 if (rect.left < offset) { 688 elem.css('left', offset - rect.left + left) 689 } else if (rect.right > window.innerWidth - offset) { 690 elem.css('left', window.innerWidth - rect.right - offset + left) 691 } 692 693 if (rect.top < offset) { 694 elem.css('top', offset - rect.top + top) 695 } else if (rect.bottom > window.innerHeight - offset) { 696 elem.css('top', window.innerHeight - rect.bottom - offset + top) 697 } 698 } 699 elem.addClass(openClass) 700 } 701 702 /** 703 * Auto initialization 704 */ 705 $(function () { 706 $('.' + prefix).socialLikes() 707 }) 708})
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.